json 1.0.4-mswin32 → 1.1.0-mswin32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of json might be problematic. Click here for more details.
- data/CHANGES +13 -2
- data/README +3 -2
- data/RUBY +58 -0
- data/Rakefile +4 -3
- data/VERSION +1 -1
- data/ext/json/ext/generator.so +0 -0
- data/ext/json/ext/generator/unicode.c +1 -1
- data/ext/json/ext/parser.so +0 -0
- data/ext/json/ext/parser/parser.c +146 -99
- data/ext/json/ext/parser/parser.rl +70 -16
- data/lib/json.rb +2 -3
- data/lib/json/common.rb +11 -2
- data/lib/json/editor.rb +1295 -1207
- data/lib/json/pure/parser.rb +35 -8
- data/lib/json/version.rb +1 -1
- data/tests/fixtures/{pass18.json → fail18.json} +0 -0
- data/tests/fixtures/{fail15.json → pass15.json} +0 -0
- data/tests/fixtures/{fail16.json → pass16.json} +0 -0
- data/tests/fixtures/{fail17.json → pass17.json} +0 -0
- data/tests/fixtures/{fail26.json → pass26.json} +0 -0
- data/tests/test_json.rb +19 -0
- data/tests/test_json_fixtures.rb +1 -1
- data/tools/fuzz.rb +1 -1
- metadata +9 -7
| @@ -2,17 +2,14 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            #include "ruby.h"
         | 
| 4 4 | 
             
            #include "re.h"
         | 
| 5 | 
            +
            #include "st.h"
         | 
| 5 6 | 
             
            #include "unicode.h"
         | 
| 6 7 |  | 
| 7 | 
            -
            #ifndef swap16
         | 
| 8 | 
            -
            #define swap16(x)	((((x)&0xFF)<<8) | (((x)>>8)&0xFF))
         | 
| 9 | 
            -
            #endif
         | 
| 10 | 
            -
             | 
| 11 8 | 
             
            #define EVIL 0x666
         | 
| 12 9 |  | 
| 13 | 
            -
            static VALUE mJSON, mExt, cParser, eParserError;
         | 
| 10 | 
            +
            static VALUE mJSON, mExt, cParser, eParserError, eNestingError;
         | 
| 14 11 |  | 
| 15 | 
            -
            static ID i_json_creatable_p, i_json_create, i_create_id, i_chr; 
         | 
| 12 | 
            +
            static ID i_json_creatable_p, i_json_create, i_create_id, i_chr, i_max_nesting; 
         | 
| 16 13 |  | 
| 17 14 | 
             
            typedef struct JSON_ParserStruct {
         | 
| 18 15 | 
             
                VALUE Vsource;
         | 
| @@ -20,6 +17,8 @@ typedef struct JSON_ParserStruct { | |
| 20 17 | 
             
                long len;
         | 
| 21 18 | 
             
                char *memo;
         | 
| 22 19 | 
             
                VALUE create_id;
         | 
| 20 | 
            +
                int max_nesting;
         | 
| 21 | 
            +
                int current_nesting;
         | 
| 23 22 | 
             
            } JSON_Parser;
         | 
| 24 23 |  | 
| 25 24 | 
             
            static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *result);
         | 
| @@ -95,6 +94,11 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu | |
| 95 94 | 
             
            {
         | 
| 96 95 | 
             
                int cs = EVIL;
         | 
| 97 96 | 
             
                VALUE last_name = Qnil;
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                if (json->max_nesting && json->current_nesting > json->max_nesting) {
         | 
| 99 | 
            +
                    rb_raise(eNestingError, "nesting of %d is to deep", json->current_nesting);
         | 
| 100 | 
            +
                }
         | 
| 101 | 
            +
             | 
| 98 102 | 
             
                *result = rb_hash_new();
         | 
| 99 103 |  | 
| 100 104 | 
             
                %% write init;
         | 
| @@ -144,12 +148,18 @@ static char *JSON_parse_object(JSON_Parser *json, char *p, char *pe, VALUE *resu | |
| 144 148 | 
             
                }
         | 
| 145 149 |  | 
| 146 150 | 
             
                action parse_array { 
         | 
| 147 | 
            -
                    char *np | 
| 151 | 
            +
                    char *np;
         | 
| 152 | 
            +
                    json->current_nesting += 1;
         | 
| 153 | 
            +
                    np = JSON_parse_array(json, fpc, pe, result);
         | 
| 154 | 
            +
                    json->current_nesting -= 1;
         | 
| 148 155 | 
             
                    if (np == NULL) fbreak; else fexec np;
         | 
| 149 156 | 
             
                }
         | 
| 150 157 |  | 
| 151 158 | 
             
                action parse_object { 
         | 
| 152 | 
            -
                    char *np | 
| 159 | 
            +
                    char *np;
         | 
| 160 | 
            +
                    json->current_nesting += 1;
         | 
| 161 | 
            +
                    np =  JSON_parse_object(json, fpc, pe, result);
         | 
| 162 | 
            +
                    json->current_nesting -= 1;
         | 
| 153 163 | 
             
                    if (np == NULL) fbreak; else fexec np;
         | 
| 154 164 | 
             
                }
         | 
| 155 165 |  | 
| @@ -269,6 +279,10 @@ static char *JSON_parse_float(JSON_Parser *json, char *p, char *pe, VALUE *resul | |
| 269 279 | 
             
            static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *result)
         | 
| 270 280 | 
             
            {
         | 
| 271 281 | 
             
                int cs = EVIL;
         | 
| 282 | 
            +
             | 
| 283 | 
            +
                if (json->max_nesting && json->current_nesting > json->max_nesting) {
         | 
| 284 | 
            +
                    rb_raise(eNestingError, "nesting of %d is to deep", json->current_nesting);
         | 
| 285 | 
            +
                }
         | 
| 272 286 | 
             
                *result = rb_ary_new();
         | 
| 273 287 |  | 
| 274 288 | 
             
                %% write init;
         | 
| @@ -281,7 +295,7 @@ static char *JSON_parse_array(JSON_Parser *json, char *p, char *pe, VALUE *resul | |
| 281 295 | 
             
                }
         | 
| 282 296 | 
             
            }
         | 
| 283 297 |  | 
| 284 | 
            -
            static VALUE  | 
| 298 | 
            +
            static VALUE json_string_unescape(char *p, char *pe)
         | 
| 285 299 | 
             
            {
         | 
| 286 300 | 
             
                VALUE result = rb_str_buf_new(pe - p + 1);
         | 
| 287 301 |  | 
| @@ -322,6 +336,10 @@ static VALUE json_string_escape(char *p, char *pe) | |
| 322 336 | 
             
                                    p = JSON_convert_UTF16_to_UTF8(result, p, pe, strictConversion);
         | 
| 323 337 | 
             
                                }
         | 
| 324 338 | 
             
                                break;
         | 
| 339 | 
            +
                            default:
         | 
| 340 | 
            +
                                rb_str_buf_cat(result, p, 1);
         | 
| 341 | 
            +
                                p++;
         | 
| 342 | 
            +
                                break;
         | 
| 325 343 | 
             
                        }
         | 
| 326 344 | 
             
                    } else {
         | 
| 327 345 | 
             
                        char *q = p;
         | 
| @@ -340,13 +358,13 @@ static VALUE json_string_escape(char *p, char *pe) | |
| 340 358 | 
             
                write data;
         | 
| 341 359 |  | 
| 342 360 | 
             
                action parse_string {
         | 
| 343 | 
            -
                    *result =  | 
| 361 | 
            +
                    *result = json_string_unescape(json->memo + 1, p);
         | 
| 344 362 | 
             
                    if (NIL_P(*result)) fbreak; else fexec p + 1;
         | 
| 345 363 | 
             
                }
         | 
| 346 364 |  | 
| 347 365 | 
             
                action exit { fbreak; }
         | 
| 348 366 |  | 
| 349 | 
            -
                main := '"' ((^(["\\] | 0..0x1f) | '\\'["\\/bfnrt] | '\\u'[0-9a-fA-F]{4})* %parse_string) '"' @exit;
         | 
| 367 | 
            +
                main := '"' ((^(["\\] | 0..0x1f) | '\\'["\\/bfnrt] | '\\u'[0-9a-fA-F]{4} | '\\'^(["\\/bfnrtu]|0..0x1f))* %parse_string) '"' @exit;
         | 
| 350 368 | 
             
            }%%
         | 
| 351 369 |  | 
| 352 370 | 
             
            static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *result)
         | 
| @@ -374,12 +392,16 @@ static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *resu | |
| 374 392 | 
             
                include JSON_common;
         | 
| 375 393 |  | 
| 376 394 | 
             
                action parse_object {
         | 
| 377 | 
            -
                    char *np | 
| 395 | 
            +
                    char *np;
         | 
| 396 | 
            +
                    json->current_nesting = 1;
         | 
| 397 | 
            +
                    np = JSON_parse_object(json, fpc, pe, &result);
         | 
| 378 398 | 
             
                    if (np == NULL) fbreak; else fexec np;
         | 
| 379 399 | 
             
                }
         | 
| 380 400 |  | 
| 381 401 | 
             
                action parse_array {
         | 
| 382 | 
            -
                    char *np | 
| 402 | 
            +
                    char *np;
         | 
| 403 | 
            +
                    json->current_nesting = 1;
         | 
| 404 | 
            +
                    np = JSON_parse_array(json, fpc, pe, &result);
         | 
| 383 405 | 
             
                    if (np == NULL) fbreak; else fexec np;
         | 
| 384 406 | 
             
                }
         | 
| 385 407 |  | 
| @@ -402,21 +424,51 @@ static char *JSON_parse_string(JSON_Parser *json, char *p, char *pe, VALUE *resu | |
| 402 424 | 
             
             */
         | 
| 403 425 |  | 
| 404 426 | 
             
            /*
         | 
| 405 | 
            -
             * call-seq: new(source)
         | 
| 427 | 
            +
             * call-seq: new(source, opts => {})
         | 
| 406 428 | 
             
             *
         | 
| 407 429 | 
             
             * Creates a new JSON::Ext::Parser instance for the string _source_.
         | 
| 430 | 
            +
             *
         | 
| 431 | 
            +
             * Creates a new JSON::Ext::Parser instance for the string _source_.
         | 
| 432 | 
            +
             *
         | 
| 433 | 
            +
             * It will be configured by the _opts_ hash. _opts_ can have the following
         | 
| 434 | 
            +
             * keys:
         | 
| 435 | 
            +
             *
         | 
| 436 | 
            +
             * _opts_ can have the following keys:
         | 
| 437 | 
            +
             * * *max_nesting*: The maximum depth of nesting allowed in the parsed data
         | 
| 438 | 
            +
             *   structures. Disable depth checking with :max_nesting => false.
         | 
| 408 439 | 
             
             */
         | 
| 409 | 
            -
            static VALUE cParser_initialize(VALUE  | 
| 440 | 
            +
            static VALUE cParser_initialize(int argc, VALUE *argv, VALUE self)
         | 
| 410 441 | 
             
            {
         | 
| 411 442 | 
             
                char *ptr;
         | 
| 412 443 | 
             
                long len;
         | 
| 444 | 
            +
                VALUE source, opts;
         | 
| 413 445 | 
             
                GET_STRUCT;
         | 
| 446 | 
            +
                rb_scan_args(argc, argv, "11", &source, &opts);
         | 
| 414 447 | 
             
                source = StringValue(source);
         | 
| 415 448 | 
             
                ptr = RSTRING_PTR(source);
         | 
| 416 449 | 
             
                len = RSTRING_LEN(source);
         | 
| 417 450 | 
             
                if (len < 2) {
         | 
| 418 451 | 
             
                    rb_raise(eParserError, "A JSON text must at least contain two octets!");
         | 
| 419 452 | 
             
                }
         | 
| 453 | 
            +
                json->max_nesting = 19;
         | 
| 454 | 
            +
                if (!NIL_P(opts)) {
         | 
| 455 | 
            +
                    opts = rb_convert_type(opts, T_HASH, "Hash", "to_hash");
         | 
| 456 | 
            +
                    if (NIL_P(opts)) {
         | 
| 457 | 
            +
                        rb_raise(rb_eArgError, "opts needs to be like a hash");
         | 
| 458 | 
            +
                    } else {
         | 
| 459 | 
            +
                        VALUE s_max_nesting = ID2SYM(i_max_nesting);
         | 
| 460 | 
            +
                        if (st_lookup(RHASH(opts)->tbl, s_max_nesting, 0)) {
         | 
| 461 | 
            +
                            VALUE max_nesting = rb_hash_aref(opts, s_max_nesting);
         | 
| 462 | 
            +
                            if (RTEST(max_nesting)) {
         | 
| 463 | 
            +
                                Check_Type(max_nesting, T_FIXNUM);
         | 
| 464 | 
            +
                                json->max_nesting = FIX2INT(max_nesting);
         | 
| 465 | 
            +
                            } else {
         | 
| 466 | 
            +
                                json->max_nesting = 0;
         | 
| 467 | 
            +
                            }
         | 
| 468 | 
            +
                        }
         | 
| 469 | 
            +
                    }
         | 
| 470 | 
            +
                }
         | 
| 471 | 
            +
                json->current_nesting = 0;
         | 
| 420 472 | 
             
                /*
         | 
| 421 473 | 
             
                   Convert these?
         | 
| 422 474 | 
             
                if (len >= 4 &&  ptr[0] == 0 && ptr[1] == 0 && ptr[2] == 0) {
         | 
| @@ -503,8 +555,9 @@ void Init_parser() | |
| 503 555 | 
             
                mExt = rb_define_module_under(mJSON, "Ext");
         | 
| 504 556 | 
             
                cParser = rb_define_class_under(mExt, "Parser", rb_cObject);
         | 
| 505 557 | 
             
                eParserError = rb_path2class("JSON::ParserError");
         | 
| 558 | 
            +
                eNestingError = rb_path2class("JSON::NestingError");
         | 
| 506 559 | 
             
                rb_define_alloc_func(cParser, cJSON_parser_s_allocate);
         | 
| 507 | 
            -
                rb_define_method(cParser, "initialize", cParser_initialize, 1);
         | 
| 560 | 
            +
                rb_define_method(cParser, "initialize", cParser_initialize, -1);
         | 
| 508 561 | 
             
                rb_define_method(cParser, "parse", cParser_parse, 0);
         | 
| 509 562 | 
             
                rb_define_method(cParser, "source", cParser_source, 0);
         | 
| 510 563 |  | 
| @@ -512,4 +565,5 @@ void Init_parser() | |
| 512 565 | 
             
                i_json_create = rb_intern("json_create");
         | 
| 513 566 | 
             
                i_create_id = rb_intern("create_id");
         | 
| 514 567 | 
             
                i_chr = rb_intern("chr");
         | 
| 568 | 
            +
                i_max_nesting = rb_intern("max_nesting");
         | 
| 515 569 | 
             
            }
         | 
    
        data/lib/json.rb
    CHANGED
    
    | @@ -34,9 +34,8 @@ require 'json/common' | |
| 34 34 | 
             
            #
         | 
| 35 35 | 
             
            # == License
         | 
| 36 36 | 
             
            #
         | 
| 37 | 
            -
            # This is  | 
| 38 | 
            -
            #  | 
| 39 | 
            -
            # Software Foundation: www.gnu.org/copyleft/gpl.html
         | 
| 37 | 
            +
            # This software is distributed under the same license as Ruby itself, see
         | 
| 38 | 
            +
            # http://www.ruby-lang.org/en/LICENSE.txt.
         | 
| 40 39 | 
             
            #
         | 
| 41 40 | 
             
            # == Download
         | 
| 42 41 | 
             
            #
         | 
    
        data/lib/json/common.rb
    CHANGED
    
    | @@ -77,6 +77,10 @@ module JSON | |
| 77 77 | 
             
              # This exception is raised, if a parser error occurs.
         | 
| 78 78 | 
             
              class ParserError < JSONError; end
         | 
| 79 79 |  | 
| 80 | 
            +
              # This exception is raised, if the nesting of parsed datastructures is too
         | 
| 81 | 
            +
              # deep.
         | 
| 82 | 
            +
              class NestingError < ParserError; end
         | 
| 83 | 
            +
             | 
| 80 84 | 
             
              # This exception is raised, if a generator or unparser error occurs.
         | 
| 81 85 | 
             
              class GeneratorError < JSONError; end
         | 
| 82 86 | 
             
              # For backwards compatibility
         | 
| @@ -93,8 +97,13 @@ module JSON | |
| 93 97 | 
             
              module_function
         | 
| 94 98 |  | 
| 95 99 | 
             
              # Parse the JSON string _source_ into a Ruby data structure and return it.
         | 
| 96 | 
            -
               | 
| 97 | 
            -
             | 
| 100 | 
            +
              #
         | 
| 101 | 
            +
              # _opts_ can have the following
         | 
| 102 | 
            +
              # keys:
         | 
| 103 | 
            +
              # * *max_nesting*: The maximum depth of nesting allowed in the parsed data
         | 
| 104 | 
            +
              #   structures. Disable depth checking with :max_nesting => false.
         | 
| 105 | 
            +
              def parse(source, opts = {})
         | 
| 106 | 
            +
                JSON.parser.new(source, opts).parse
         | 
| 98 107 | 
             
              end
         | 
| 99 108 |  | 
| 100 109 | 
             
              # Unparse the Ruby data structure _obj_ into a single line JSON string and
         | 
    
        data/lib/json/editor.rb
    CHANGED
    
    | @@ -1,1207 +1,1295 @@ | |
| 1 | 
            -
            # To use the GUI JSON editor, start the edit_json.rb executable script. It
         | 
| 2 | 
            -
            # requires ruby-gtk to be installed.
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            require 'gtk2'
         | 
| 5 | 
            -
            require 'iconv'
         | 
| 6 | 
            -
            require 'json'
         | 
| 7 | 
            -
            require 'rbconfig'
         | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
                 | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
                 | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
                 | 
| 21 | 
            -
                 | 
| 22 | 
            -
                 | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
                 | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
                 | 
| 30 | 
            -
                   | 
| 31 | 
            -
                   | 
| 32 | 
            -
                     | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
                end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                #  | 
| 39 | 
            -
                 | 
| 40 | 
            -
             | 
| 41 | 
            -
                   | 
| 42 | 
            -
                     | 
| 43 | 
            -
                     | 
| 44 | 
            -
                   | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
                 | 
| 51 | 
            -
                  dialog. | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
                 | 
| 58 | 
            -
                  dialog = MessageDialog.new(window, Dialog::MODAL, 
         | 
| 59 | 
            -
                    MessageDialog:: | 
| 60 | 
            -
                    MessageDialog:: | 
| 61 | 
            -
                  dialog. | 
| 62 | 
            -
             | 
| 63 | 
            -
                   | 
| 64 | 
            -
                ensure
         | 
| 65 | 
            -
                  dialog.destroy if dialog
         | 
| 66 | 
            -
                end
         | 
| 67 | 
            -
             | 
| 68 | 
            -
                #  | 
| 69 | 
            -
                #  | 
| 70 | 
            -
                 | 
| 71 | 
            -
             | 
| 72 | 
            -
                   | 
| 73 | 
            -
                     | 
| 74 | 
            -
                     | 
| 75 | 
            -
             | 
| 76 | 
            -
                   | 
| 77 | 
            -
             | 
| 78 | 
            -
                     | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
                     | 
| 91 | 
            -
             | 
| 92 | 
            -
                     | 
| 93 | 
            -
                  when ' | 
| 94 | 
            -
                     | 
| 95 | 
            -
             | 
| 96 | 
            -
                     | 
| 97 | 
            -
                   | 
| 98 | 
            -
                     | 
| 99 | 
            -
                   | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
                   | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 112 | 
            -
             | 
| 113 | 
            -
             | 
| 114 | 
            -
                   | 
| 115 | 
            -
             | 
| 116 | 
            -
             | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 125 | 
            -
             | 
| 126 | 
            -
             | 
| 127 | 
            -
                     | 
| 128 | 
            -
                   | 
| 129 | 
            -
                     | 
| 130 | 
            -
             | 
| 131 | 
            -
                   | 
| 132 | 
            -
             | 
| 133 | 
            -
                    iter. | 
| 134 | 
            -
             | 
| 135 | 
            -
             | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 138 | 
            -
             | 
| 139 | 
            -
             | 
| 140 | 
            -
             | 
| 141 | 
            -
             | 
| 142 | 
            -
             | 
| 143 | 
            -
             | 
| 144 | 
            -
             | 
| 145 | 
            -
                   | 
| 146 | 
            -
             | 
| 147 | 
            -
             | 
| 148 | 
            -
             | 
| 149 | 
            -
             | 
| 150 | 
            -
             | 
| 151 | 
            -
                   | 
| 152 | 
            -
             | 
| 153 | 
            -
             | 
| 154 | 
            -
             | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 160 | 
            -
                   | 
| 161 | 
            -
             | 
| 162 | 
            -
                   | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 165 | 
            -
                     | 
| 166 | 
            -
                  end
         | 
| 167 | 
            -
             | 
| 168 | 
            -
                  #  | 
| 169 | 
            -
                   | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
             | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 175 | 
            -
                   | 
| 176 | 
            -
             | 
| 177 | 
            -
             | 
| 178 | 
            -
                   | 
| 179 | 
            -
             | 
| 180 | 
            -
             | 
| 181 | 
            -
             | 
| 182 | 
            -
                     | 
| 183 | 
            -
                  end
         | 
| 184 | 
            -
             | 
| 185 | 
            -
                  #  | 
| 186 | 
            -
                  def  | 
| 187 | 
            -
                    self[ | 
| 188 | 
            -
                  end
         | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 193 | 
            -
             | 
| 194 | 
            -
             | 
| 195 | 
            -
             | 
| 196 | 
            -
             | 
| 197 | 
            -
                  #  | 
| 198 | 
            -
                  def  | 
| 199 | 
            -
                     | 
| 200 | 
            -
             | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
                   | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 212 | 
            -
             | 
| 213 | 
            -
             | 
| 214 | 
            -
                  #  | 
| 215 | 
            -
                   | 
| 216 | 
            -
             | 
| 217 | 
            -
             | 
| 218 | 
            -
             | 
| 219 | 
            -
             | 
| 220 | 
            -
             | 
| 221 | 
            -
             | 
| 222 | 
            -
             | 
| 223 | 
            -
             | 
| 224 | 
            -
                   | 
| 225 | 
            -
             | 
| 226 | 
            -
                  #  | 
| 227 | 
            -
                  def  | 
| 228 | 
            -
                     | 
| 229 | 
            -
                  end
         | 
| 230 | 
            -
             | 
| 231 | 
            -
                   | 
| 232 | 
            -
             | 
| 233 | 
            -
                   | 
| 234 | 
            -
             | 
| 235 | 
            -
             | 
| 236 | 
            -
             | 
| 237 | 
            -
             | 
| 238 | 
            -
             | 
| 239 | 
            -
             | 
| 240 | 
            -
             | 
| 241 | 
            -
             | 
| 242 | 
            -
             | 
| 243 | 
            -
             | 
| 244 | 
            -
                       | 
| 245 | 
            -
             | 
| 246 | 
            -
             | 
| 247 | 
            -
             | 
| 248 | 
            -
             | 
| 249 | 
            -
             | 
| 250 | 
            -
             | 
| 251 | 
            -
             | 
| 252 | 
            -
             | 
| 253 | 
            -
             | 
| 254 | 
            -
             | 
| 255 | 
            -
             | 
| 256 | 
            -
             | 
| 257 | 
            -
             | 
| 258 | 
            -
             | 
| 259 | 
            -
             | 
| 260 | 
            -
             | 
| 261 | 
            -
             | 
| 262 | 
            -
             | 
| 263 | 
            -
             | 
| 264 | 
            -
             | 
| 265 | 
            -
                   | 
| 266 | 
            -
             | 
| 267 | 
            -
             | 
| 268 | 
            -
             | 
| 269 | 
            -
             | 
| 270 | 
            -
             | 
| 271 | 
            -
                       | 
| 272 | 
            -
             | 
| 273 | 
            -
             | 
| 274 | 
            -
             | 
| 275 | 
            -
             | 
| 276 | 
            -
             | 
| 277 | 
            -
             | 
| 278 | 
            -
             | 
| 279 | 
            -
             | 
| 280 | 
            -
             | 
| 281 | 
            -
             | 
| 282 | 
            -
             | 
| 283 | 
            -
             | 
| 284 | 
            -
             | 
| 285 | 
            -
             | 
| 286 | 
            -
             | 
| 287 | 
            -
             | 
| 288 | 
            -
             | 
| 289 | 
            -
             | 
| 290 | 
            -
             | 
| 291 | 
            -
             | 
| 292 | 
            -
             | 
| 293 | 
            -
             | 
| 294 | 
            -
             | 
| 295 | 
            -
             | 
| 296 | 
            -
             | 
| 297 | 
            -
             | 
| 298 | 
            -
             | 
| 299 | 
            -
             | 
| 300 | 
            -
                       | 
| 301 | 
            -
             | 
| 302 | 
            -
             | 
| 303 | 
            -
             | 
| 304 | 
            -
             | 
| 305 | 
            -
             | 
| 306 | 
            -
             | 
| 307 | 
            -
             | 
| 308 | 
            -
             | 
| 309 | 
            -
             | 
| 310 | 
            -
             | 
| 311 | 
            -
             | 
| 312 | 
            -
             | 
| 313 | 
            -
             | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 316 | 
            -
             | 
| 317 | 
            -
             | 
| 318 | 
            -
             | 
| 319 | 
            -
             | 
| 320 | 
            -
             | 
| 321 | 
            -
             | 
| 322 | 
            -
             | 
| 323 | 
            -
             | 
| 324 | 
            -
             | 
| 325 | 
            -
             | 
| 326 | 
            -
                       | 
| 327 | 
            -
                         | 
| 328 | 
            -
             | 
| 329 | 
            -
             | 
| 330 | 
            -
             | 
| 331 | 
            -
             | 
| 332 | 
            -
             | 
| 333 | 
            -
             | 
| 334 | 
            -
             | 
| 335 | 
            -
             | 
| 336 | 
            -
             | 
| 337 | 
            -
             | 
| 338 | 
            -
             | 
| 339 | 
            -
             | 
| 340 | 
            -
             | 
| 341 | 
            -
             | 
| 342 | 
            -
             | 
| 343 | 
            -
                             | 
| 344 | 
            -
                           | 
| 345 | 
            -
             | 
| 346 | 
            -
             | 
| 347 | 
            -
                          end
         | 
| 348 | 
            -
             | 
| 349 | 
            -
                          toplevel.display_status( | 
| 350 | 
            -
                            " | 
| 351 | 
            -
             | 
| 352 | 
            -
             | 
| 353 | 
            -
             | 
| 354 | 
            -
             | 
| 355 | 
            -
             | 
| 356 | 
            -
             | 
| 357 | 
            -
             | 
| 358 | 
            -
             | 
| 359 | 
            -
             | 
| 360 | 
            -
             | 
| 361 | 
            -
             | 
| 362 | 
            -
                   | 
| 363 | 
            -
             | 
| 364 | 
            -
             | 
| 365 | 
            -
             | 
| 366 | 
            -
             | 
| 367 | 
            -
             | 
| 368 | 
            -
             | 
| 369 | 
            -
             | 
| 370 | 
            -
             | 
| 371 | 
            -
             | 
| 372 | 
            -
             | 
| 373 | 
            -
             | 
| 374 | 
            -
             | 
| 375 | 
            -
                          " | 
| 376 | 
            -
             | 
| 377 | 
            -
             | 
| 378 | 
            -
                         | 
| 379 | 
            -
             | 
| 380 | 
            -
             | 
| 381 | 
            -
                         | 
| 382 | 
            -
             | 
| 383 | 
            -
             | 
| 384 | 
            -
             | 
| 385 | 
            -
             | 
| 386 | 
            -
             | 
| 387 | 
            -
             | 
| 388 | 
            -
             | 
| 389 | 
            -
             | 
| 390 | 
            -
             | 
| 391 | 
            -
             | 
| 392 | 
            -
             | 
| 393 | 
            -
             | 
| 394 | 
            -
             | 
| 395 | 
            -
             | 
| 396 | 
            -
             | 
| 397 | 
            -
             | 
| 398 | 
            -
             | 
| 399 | 
            -
             | 
| 400 | 
            -
             | 
| 401 | 
            -
             | 
| 402 | 
            -
             | 
| 403 | 
            -
             | 
| 404 | 
            -
                        type, content = ask_for_element(parent)
         | 
| 405 | 
            -
                        type or return
         | 
| 406 | 
            -
                        iter =  | 
| 407 | 
            -
                         | 
| 408 | 
            -
                        toplevel.display_status(" | 
| 409 | 
            -
             | 
| 410 | 
            -
                         | 
| 411 | 
            -
                       | 
| 412 | 
            -
             | 
| 413 | 
            -
             | 
| 414 | 
            -
                       | 
| 415 | 
            -
             | 
| 416 | 
            -
             | 
| 417 | 
            -
                    end
         | 
| 418 | 
            -
                  end
         | 
| 419 | 
            -
             | 
| 420 | 
            -
                  #  | 
| 421 | 
            -
                  def  | 
| 422 | 
            -
                    if current = selection.selected
         | 
| 423 | 
            -
                       | 
| 424 | 
            -
             | 
| 425 | 
            -
                       | 
| 426 | 
            -
             | 
| 427 | 
            -
             | 
| 428 | 
            -
             | 
| 429 | 
            -
                         | 
| 430 | 
            -
             | 
| 431 | 
            -
             | 
| 432 | 
            -
             | 
| 433 | 
            -
             | 
| 434 | 
            -
             | 
| 435 | 
            -
             | 
| 436 | 
            -
             | 
| 437 | 
            -
             | 
| 438 | 
            -
             | 
| 439 | 
            -
             | 
| 440 | 
            -
             | 
| 441 | 
            -
             | 
| 442 | 
            -
             | 
| 443 | 
            -
                     | 
| 444 | 
            -
             | 
| 445 | 
            -
             | 
| 446 | 
            -
             | 
| 447 | 
            -
             | 
| 448 | 
            -
             | 
| 449 | 
            -
             | 
| 450 | 
            -
             | 
| 451 | 
            -
                       | 
| 452 | 
            -
                         | 
| 453 | 
            -
                      end
         | 
| 454 | 
            -
                     | 
| 455 | 
            -
             | 
| 456 | 
            -
             | 
| 457 | 
            -
             | 
| 458 | 
            -
             | 
| 459 | 
            -
             | 
| 460 | 
            -
             | 
| 461 | 
            -
             | 
| 462 | 
            -
             | 
| 463 | 
            -
             | 
| 464 | 
            -
             | 
| 465 | 
            -
             | 
| 466 | 
            -
             | 
| 467 | 
            -
             | 
| 468 | 
            -
                     | 
| 469 | 
            -
             | 
| 470 | 
            -
             | 
| 471 | 
            -
             | 
| 472 | 
            -
             | 
| 473 | 
            -
             | 
| 474 | 
            -
             | 
| 475 | 
            -
             | 
| 476 | 
            -
             | 
| 477 | 
            -
             | 
| 478 | 
            -
             | 
| 479 | 
            -
             | 
| 480 | 
            -
             | 
| 481 | 
            -
                     | 
| 482 | 
            -
             | 
| 483 | 
            -
             | 
| 484 | 
            -
                   | 
| 485 | 
            -
             | 
| 486 | 
            -
             | 
| 487 | 
            -
             | 
| 488 | 
            -
             | 
| 489 | 
            -
                   | 
| 490 | 
            -
             | 
| 491 | 
            -
             | 
| 492 | 
            -
                   | 
| 493 | 
            -
             | 
| 494 | 
            -
             | 
| 495 | 
            -
                   | 
| 496 | 
            -
             | 
| 497 | 
            -
                   | 
| 498 | 
            -
             | 
| 499 | 
            -
                   | 
| 500 | 
            -
             | 
| 501 | 
            -
             | 
| 502 | 
            -
             | 
| 503 | 
            -
             | 
| 504 | 
            -
                     | 
| 505 | 
            -
             | 
| 506 | 
            -
             | 
| 507 | 
            -
             | 
| 508 | 
            -
             | 
| 509 | 
            -
                     | 
| 510 | 
            -
             | 
| 511 | 
            -
                     | 
| 512 | 
            -
                  end
         | 
| 513 | 
            -
             | 
| 514 | 
            -
             | 
| 515 | 
            -
             | 
| 516 | 
            -
             | 
| 517 | 
            -
                   | 
| 518 | 
            -
             | 
| 519 | 
            -
                  #  | 
| 520 | 
            -
                   | 
| 521 | 
            -
             | 
| 522 | 
            -
             | 
| 523 | 
            -
             | 
| 524 | 
            -
             | 
| 525 | 
            -
             | 
| 526 | 
            -
             | 
| 527 | 
            -
             | 
| 528 | 
            -
             | 
| 529 | 
            -
             | 
| 530 | 
            -
             | 
| 531 | 
            -
             | 
| 532 | 
            -
             | 
| 533 | 
            -
             | 
| 534 | 
            -
             | 
| 535 | 
            -
             | 
| 536 | 
            -
             | 
| 537 | 
            -
             | 
| 538 | 
            -
             | 
| 539 | 
            -
             | 
| 540 | 
            -
             | 
| 541 | 
            -
             | 
| 542 | 
            -
             | 
| 543 | 
            -
             | 
| 544 | 
            -
             | 
| 545 | 
            -
             | 
| 546 | 
            -
             | 
| 547 | 
            -
             | 
| 548 | 
            -
             | 
| 549 | 
            -
             | 
| 550 | 
            -
             | 
| 551 | 
            -
             | 
| 552 | 
            -
             | 
| 553 | 
            -
             | 
| 554 | 
            -
             | 
| 555 | 
            -
             | 
| 556 | 
            -
             | 
| 557 | 
            -
             | 
| 558 | 
            -
                       | 
| 559 | 
            -
             | 
| 560 | 
            -
             | 
| 561 | 
            -
             | 
| 562 | 
            -
                       | 
| 563 | 
            -
             | 
| 564 | 
            -
             | 
| 565 | 
            -
             | 
| 566 | 
            -
             | 
| 567 | 
            -
             | 
| 568 | 
            -
             | 
| 569 | 
            -
             | 
| 570 | 
            -
             | 
| 571 | 
            -
             | 
| 572 | 
            -
             | 
| 573 | 
            -
             | 
| 574 | 
            -
             | 
| 575 | 
            -
             | 
| 576 | 
            -
             | 
| 577 | 
            -
             | 
| 578 | 
            -
             | 
| 579 | 
            -
             | 
| 580 | 
            -
             | 
| 581 | 
            -
             | 
| 582 | 
            -
             | 
| 583 | 
            -
             | 
| 584 | 
            -
             | 
| 585 | 
            -
             | 
| 586 | 
            -
             | 
| 587 | 
            -
                           | 
| 588 | 
            -
             | 
| 589 | 
            -
             | 
| 590 | 
            -
             | 
| 591 | 
            -
             | 
| 592 | 
            -
             | 
| 593 | 
            -
                       | 
| 594 | 
            -
             | 
| 595 | 
            -
             | 
| 596 | 
            -
             | 
| 597 | 
            -
             | 
| 598 | 
            -
             | 
| 599 | 
            -
                   | 
| 600 | 
            -
             | 
| 601 | 
            -
             | 
| 602 | 
            -
             | 
| 603 | 
            -
             | 
| 604 | 
            -
             | 
| 605 | 
            -
             | 
| 606 | 
            -
             | 
| 607 | 
            -
             | 
| 608 | 
            -
             | 
| 609 | 
            -
             | 
| 610 | 
            -
             | 
| 611 | 
            -
             | 
| 612 | 
            -
             | 
| 613 | 
            -
             | 
| 614 | 
            -
             | 
| 615 | 
            -
             | 
| 616 | 
            -
             | 
| 617 | 
            -
             | 
| 618 | 
            -
             | 
| 619 | 
            -
             | 
| 620 | 
            -
             | 
| 621 | 
            -
             | 
| 622 | 
            -
             | 
| 623 | 
            -
             | 
| 624 | 
            -
             | 
| 625 | 
            -
             | 
| 626 | 
            -
             | 
| 627 | 
            -
             | 
| 628 | 
            -
             | 
| 629 | 
            -
                     | 
| 630 | 
            -
             | 
| 631 | 
            -
             | 
| 632 | 
            -
             | 
| 633 | 
            -
                   | 
| 634 | 
            -
             | 
| 635 | 
            -
             | 
| 636 | 
            -
             | 
| 637 | 
            -
                     | 
| 638 | 
            -
                     | 
| 639 | 
            -
                    add_item(' | 
| 640 | 
            -
                     | 
| 641 | 
            -
             | 
| 642 | 
            -
             | 
| 643 | 
            -
             | 
| 644 | 
            -
             | 
| 645 | 
            -
                   | 
| 646 | 
            -
             | 
| 647 | 
            -
             | 
| 648 | 
            -
             | 
| 649 | 
            -
             | 
| 650 | 
            -
             | 
| 651 | 
            -
             | 
| 652 | 
            -
             | 
| 653 | 
            -
             | 
| 654 | 
            -
             | 
| 655 | 
            -
             | 
| 656 | 
            -
             | 
| 657 | 
            -
             | 
| 658 | 
            -
             | 
| 659 | 
            -
             | 
| 660 | 
            -
                    @ | 
| 661 | 
            -
                     | 
| 662 | 
            -
             | 
| 663 | 
            -
             | 
| 664 | 
            -
                   | 
| 665 | 
            -
             | 
| 666 | 
            -
                  #  | 
| 667 | 
            -
                   | 
| 668 | 
            -
             | 
| 669 | 
            -
             | 
| 670 | 
            -
             | 
| 671 | 
            -
             | 
| 672 | 
            -
             | 
| 673 | 
            -
             | 
| 674 | 
            -
             | 
| 675 | 
            -
                     | 
| 676 | 
            -
             | 
| 677 | 
            -
             | 
| 678 | 
            -
             | 
| 679 | 
            -
             | 
| 680 | 
            -
             | 
| 681 | 
            -
             | 
| 682 | 
            -
             | 
| 683 | 
            -
             | 
| 684 | 
            -
             | 
| 685 | 
            -
             | 
| 686 | 
            -
             | 
| 687 | 
            -
                     | 
| 688 | 
            -
                     | 
| 689 | 
            -
                     | 
| 690 | 
            -
             | 
| 691 | 
            -
                     | 
| 692 | 
            -
                     | 
| 693 | 
            -
                     | 
| 694 | 
            -
             | 
| 695 | 
            -
             | 
| 696 | 
            -
             | 
| 697 | 
            -
             | 
| 698 | 
            -
             | 
| 699 | 
            -
             | 
| 700 | 
            -
             | 
| 701 | 
            -
             | 
| 702 | 
            -
             | 
| 703 | 
            -
             | 
| 704 | 
            -
             | 
| 705 | 
            -
             | 
| 706 | 
            -
                     | 
| 707 | 
            -
             | 
| 708 | 
            -
             | 
| 709 | 
            -
             | 
| 710 | 
            -
                     | 
| 711 | 
            -
             | 
| 712 | 
            -
                     | 
| 713 | 
            -
             | 
| 714 | 
            -
                       | 
| 715 | 
            -
                     | 
| 716 | 
            -
             | 
| 717 | 
            -
             | 
| 718 | 
            -
             | 
| 719 | 
            -
             | 
| 720 | 
            -
                     | 
| 721 | 
            -
                       | 
| 722 | 
            -
             | 
| 723 | 
            -
             | 
| 724 | 
            -
             | 
| 725 | 
            -
             | 
| 726 | 
            -
             | 
| 727 | 
            -
             | 
| 728 | 
            -
             | 
| 729 | 
            -
                     | 
| 730 | 
            -
             | 
| 731 | 
            -
             | 
| 732 | 
            -
                       | 
| 733 | 
            -
             | 
| 734 | 
            -
             | 
| 735 | 
            -
             | 
| 736 | 
            -
             | 
| 737 | 
            -
             | 
| 738 | 
            -
             | 
| 739 | 
            -
             | 
| 740 | 
            -
             | 
| 741 | 
            -
             | 
| 742 | 
            -
                     | 
| 743 | 
            -
             | 
| 744 | 
            -
             | 
| 745 | 
            -
                       | 
| 746 | 
            -
                    when ' | 
| 747 | 
            -
                      value. | 
| 748 | 
            -
             | 
| 749 | 
            -
             | 
| 750 | 
            -
                       | 
| 751 | 
            -
                     | 
| 752 | 
            -
                       | 
| 753 | 
            -
             | 
| 754 | 
            -
             | 
| 755 | 
            -
             | 
| 756 | 
            -
             | 
| 757 | 
            -
             | 
| 758 | 
            -
                     | 
| 759 | 
            -
             | 
| 760 | 
            -
             | 
| 761 | 
            -
             | 
| 762 | 
            -
             | 
| 763 | 
            -
             | 
| 764 | 
            -
             | 
| 765 | 
            -
             | 
| 766 | 
            -
                   | 
| 767 | 
            -
             | 
| 768 | 
            -
             | 
| 769 | 
            -
                     | 
| 770 | 
            -
             | 
| 771 | 
            -
             | 
| 772 | 
            -
                       | 
| 773 | 
            -
                     | 
| 774 | 
            -
             | 
| 775 | 
            -
                     | 
| 776 | 
            -
             | 
| 777 | 
            -
             | 
| 778 | 
            -
             | 
| 779 | 
            -
             | 
| 780 | 
            -
             | 
| 781 | 
            -
             | 
| 782 | 
            -
             | 
| 783 | 
            -
             | 
| 784 | 
            -
             | 
| 785 | 
            -
             | 
| 786 | 
            -
             | 
| 787 | 
            -
             | 
| 788 | 
            -
                     | 
| 789 | 
            -
                     | 
| 790 | 
            -
             | 
| 791 | 
            -
             | 
| 792 | 
            -
             | 
| 793 | 
            -
             | 
| 794 | 
            -
             | 
| 795 | 
            -
             | 
| 796 | 
            -
             | 
| 797 | 
            -
             | 
| 798 | 
            -
             | 
| 799 | 
            -
                       | 
| 800 | 
            -
                     | 
| 801 | 
            -
             | 
| 802 | 
            -
             | 
| 803 | 
            -
             | 
| 804 | 
            -
                     | 
| 805 | 
            -
                     | 
| 806 | 
            -
                     | 
| 807 | 
            -
                     | 
| 808 | 
            -
             | 
| 809 | 
            -
             | 
| 810 | 
            -
             | 
| 811 | 
            -
             | 
| 812 | 
            -
             | 
| 813 | 
            -
             | 
| 814 | 
            -
             | 
| 815 | 
            -
             | 
| 816 | 
            -
                       | 
| 817 | 
            -
             | 
| 818 | 
            -
             | 
| 819 | 
            -
             | 
| 820 | 
            -
             | 
| 821 | 
            -
             | 
| 822 | 
            -
             | 
| 823 | 
            -
             | 
| 824 | 
            -
                     | 
| 825 | 
            -
             | 
| 826 | 
            -
             | 
| 827 | 
            -
             | 
| 828 | 
            -
             | 
| 829 | 
            -
             | 
| 830 | 
            -
             | 
| 831 | 
            -
             | 
| 832 | 
            -
             | 
| 833 | 
            -
                     | 
| 834 | 
            -
             | 
| 835 | 
            -
             | 
| 836 | 
            -
             | 
| 837 | 
            -
             | 
| 838 | 
            -
             | 
| 839 | 
            -
             | 
| 840 | 
            -
                     | 
| 841 | 
            -
             | 
| 842 | 
            -
             | 
| 843 | 
            -
             | 
| 844 | 
            -
             | 
| 845 | 
            -
             | 
| 846 | 
            -
             | 
| 847 | 
            -
             | 
| 848 | 
            -
             | 
| 849 | 
            -
             | 
| 850 | 
            -
             | 
| 851 | 
            -
                       | 
| 852 | 
            -
             | 
| 853 | 
            -
             | 
| 854 | 
            -
                       | 
| 855 | 
            -
                     | 
| 856 | 
            -
             | 
| 857 | 
            -
                    hbox. | 
| 858 | 
            -
                    hbox. | 
| 859 | 
            -
                     | 
| 860 | 
            -
                     | 
| 861 | 
            -
                     | 
| 862 | 
            -
             | 
| 863 | 
            -
             | 
| 864 | 
            -
             | 
| 865 | 
            -
             | 
| 866 | 
            -
                     | 
| 867 | 
            -
             | 
| 868 | 
            -
             | 
| 869 | 
            -
             | 
| 870 | 
            -
             | 
| 871 | 
            -
             | 
| 872 | 
            -
             | 
| 873 | 
            -
                     | 
| 874 | 
            -
                     | 
| 875 | 
            -
             | 
| 876 | 
            -
                     | 
| 877 | 
            -
             | 
| 878 | 
            -
             | 
| 879 | 
            -
             | 
| 880 | 
            -
             | 
| 881 | 
            -
                     | 
| 882 | 
            -
             | 
| 883 | 
            -
             | 
| 884 | 
            -
             | 
| 885 | 
            -
             | 
| 886 | 
            -
             | 
| 887 | 
            -
             | 
| 888 | 
            -
             | 
| 889 | 
            -
             | 
| 890 | 
            -
             | 
| 891 | 
            -
             | 
| 892 | 
            -
             | 
| 893 | 
            -
                     | 
| 894 | 
            -
                     | 
| 895 | 
            -
             | 
| 896 | 
            -
             | 
| 897 | 
            -
             | 
| 898 | 
            -
             | 
| 899 | 
            -
             | 
| 900 | 
            -
             | 
| 901 | 
            -
             | 
| 902 | 
            -
             | 
| 903 | 
            -
             | 
| 904 | 
            -
             | 
| 905 | 
            -
             | 
| 906 | 
            -
             | 
| 907 | 
            -
             | 
| 908 | 
            -
                    )
         | 
| 909 | 
            -
                     | 
| 910 | 
            -
             | 
| 911 | 
            -
             | 
| 912 | 
            -
                     | 
| 913 | 
            -
             | 
| 914 | 
            -
             | 
| 915 | 
            -
                     | 
| 916 | 
            -
             | 
| 917 | 
            -
                    dialog. | 
| 918 | 
            -
             | 
| 919 | 
            -
             | 
| 920 | 
            -
             | 
| 921 | 
            -
             | 
| 922 | 
            -
             | 
| 923 | 
            -
             | 
| 924 | 
            -
             | 
| 925 | 
            -
             | 
| 926 | 
            -
             | 
| 927 | 
            -
             | 
| 928 | 
            -
             | 
| 929 | 
            -
             | 
| 930 | 
            -
                   | 
| 931 | 
            -
             | 
| 932 | 
            -
                   | 
| 933 | 
            -
             | 
| 934 | 
            -
             | 
| 935 | 
            -
             | 
| 936 | 
            -
             | 
| 937 | 
            -
             | 
| 938 | 
            -
                     | 
| 939 | 
            -
             | 
| 940 | 
            -
             | 
| 941 | 
            -
             | 
| 942 | 
            -
             | 
| 943 | 
            -
                     | 
| 944 | 
            -
             | 
| 945 | 
            -
             | 
| 946 | 
            -
             | 
| 947 | 
            -
                     | 
| 948 | 
            -
                     | 
| 949 | 
            -
             | 
| 950 | 
            -
             | 
| 951 | 
            -
             | 
| 952 | 
            -
                     | 
| 953 | 
            -
             | 
| 954 | 
            -
             | 
| 955 | 
            -
                    dialog. | 
| 956 | 
            -
             | 
| 957 | 
            -
             | 
| 958 | 
            -
             | 
| 959 | 
            -
             | 
| 960 | 
            -
             | 
| 961 | 
            -
                     | 
| 962 | 
            -
             | 
| 963 | 
            -
             | 
| 964 | 
            -
             | 
| 965 | 
            -
             | 
| 966 | 
            -
             | 
| 967 | 
            -
             | 
| 968 | 
            -
             | 
| 969 | 
            -
             | 
| 970 | 
            -
             | 
| 971 | 
            -
             | 
| 972 | 
            -
             | 
| 973 | 
            -
             | 
| 974 | 
            -
             | 
| 975 | 
            -
                     | 
| 976 | 
            -
                     | 
| 977 | 
            -
             | 
| 978 | 
            -
                     | 
| 979 | 
            -
                     | 
| 980 | 
            -
             | 
| 981 | 
            -
             | 
| 982 | 
            -
                    add( | 
| 983 | 
            -
             | 
| 984 | 
            -
             | 
| 985 | 
            -
                     | 
| 986 | 
            -
                     | 
| 987 | 
            -
             | 
| 988 | 
            -
             | 
| 989 | 
            -
             | 
| 990 | 
            -
             | 
| 991 | 
            -
                     | 
| 992 | 
            -
             | 
| 993 | 
            -
             | 
| 994 | 
            -
                     | 
| 995 | 
            -
             | 
| 996 | 
            -
             | 
| 997 | 
            -
             | 
| 998 | 
            -
             | 
| 999 | 
            -
             | 
| 1000 | 
            -
                     | 
| 1001 | 
            -
             | 
| 1002 | 
            -
                     | 
| 1003 | 
            -
             | 
| 1004 | 
            -
             | 
| 1005 | 
            -
             | 
| 1006 | 
            -
             | 
| 1007 | 
            -
             | 
| 1008 | 
            -
             | 
| 1009 | 
            -
             | 
| 1010 | 
            -
                   | 
| 1011 | 
            -
             | 
| 1012 | 
            -
             | 
| 1013 | 
            -
                     | 
| 1014 | 
            -
                    @ | 
| 1015 | 
            -
                     | 
| 1016 | 
            -
                     | 
| 1017 | 
            -
                     | 
| 1018 | 
            -
                     | 
| 1019 | 
            -
             | 
| 1020 | 
            -
             | 
| 1021 | 
            -
             | 
| 1022 | 
            -
             | 
| 1023 | 
            -
             | 
| 1024 | 
            -
                    @ | 
| 1025 | 
            -
                     | 
| 1026 | 
            -
             | 
| 1027 | 
            -
             | 
| 1028 | 
            -
             | 
| 1029 | 
            -
             | 
| 1030 | 
            -
             | 
| 1031 | 
            -
             | 
| 1032 | 
            -
                     | 
| 1033 | 
            -
             | 
| 1034 | 
            -
             | 
| 1035 | 
            -
             | 
| 1036 | 
            -
             | 
| 1037 | 
            -
             | 
| 1038 | 
            -
                    @ | 
| 1039 | 
            -
                    @ | 
| 1040 | 
            -
             | 
| 1041 | 
            -
             | 
| 1042 | 
            -
             | 
| 1043 | 
            -
             | 
| 1044 | 
            -
             | 
| 1045 | 
            -
                     | 
| 1046 | 
            -
             | 
| 1047 | 
            -
             | 
| 1048 | 
            -
             | 
| 1049 | 
            -
                   | 
| 1050 | 
            -
             | 
| 1051 | 
            -
             | 
| 1052 | 
            -
             | 
| 1053 | 
            -
                     | 
| 1054 | 
            -
             | 
| 1055 | 
            -
             | 
| 1056 | 
            -
             | 
| 1057 | 
            -
             | 
| 1058 | 
            -
             | 
| 1059 | 
            -
             | 
| 1060 | 
            -
             | 
| 1061 | 
            -
                   | 
| 1062 | 
            -
             | 
| 1063 | 
            -
             | 
| 1064 | 
            -
             | 
| 1065 | 
            -
             | 
| 1066 | 
            -
             | 
| 1067 | 
            -
             | 
| 1068 | 
            -
             | 
| 1069 | 
            -
                   | 
| 1070 | 
            -
             | 
| 1071 | 
            -
             | 
| 1072 | 
            -
                   | 
| 1073 | 
            -
             | 
| 1074 | 
            -
             | 
| 1075 | 
            -
             | 
| 1076 | 
            -
                     | 
| 1077 | 
            -
             | 
| 1078 | 
            -
             | 
| 1079 | 
            -
             | 
| 1080 | 
            -
                   | 
| 1081 | 
            -
             | 
| 1082 | 
            -
             | 
| 1083 | 
            -
             | 
| 1084 | 
            -
             | 
| 1085 | 
            -
             | 
| 1086 | 
            -
             | 
| 1087 | 
            -
             | 
| 1088 | 
            -
                   | 
| 1089 | 
            -
             | 
| 1090 | 
            -
             | 
| 1091 | 
            -
             | 
| 1092 | 
            -
             | 
| 1093 | 
            -
             | 
| 1094 | 
            -
             | 
| 1095 | 
            -
             | 
| 1096 | 
            -
             | 
| 1097 | 
            -
             | 
| 1098 | 
            -
             | 
| 1099 | 
            -
             | 
| 1100 | 
            -
             | 
| 1101 | 
            -
             | 
| 1102 | 
            -
             | 
| 1103 | 
            -
                   | 
| 1104 | 
            -
             | 
| 1105 | 
            -
                     | 
| 1106 | 
            -
                     | 
| 1107 | 
            -
             | 
| 1108 | 
            -
             | 
| 1109 | 
            -
             | 
| 1110 | 
            -
                   | 
| 1111 | 
            -
             | 
| 1112 | 
            -
             | 
| 1113 | 
            -
             | 
| 1114 | 
            -
             | 
| 1115 | 
            -
             | 
| 1116 | 
            -
             | 
| 1117 | 
            -
             | 
| 1118 | 
            -
             | 
| 1119 | 
            -
             | 
| 1120 | 
            -
             | 
| 1121 | 
            -
             | 
| 1122 | 
            -
             | 
| 1123 | 
            -
             | 
| 1124 | 
            -
             | 
| 1125 | 
            -
                   | 
| 1126 | 
            -
                     | 
| 1127 | 
            -
             | 
| 1128 | 
            -
             | 
| 1129 | 
            -
                   | 
| 1130 | 
            -
             | 
| 1131 | 
            -
             | 
| 1132 | 
            -
             | 
| 1133 | 
            -
             | 
| 1134 | 
            -
             | 
| 1135 | 
            -
             | 
| 1136 | 
            -
             | 
| 1137 | 
            -
             | 
| 1138 | 
            -
             | 
| 1139 | 
            -
             | 
| 1140 | 
            -
             | 
| 1141 | 
            -
             | 
| 1142 | 
            -
             | 
| 1143 | 
            -
             | 
| 1144 | 
            -
             | 
| 1145 | 
            -
             | 
| 1146 | 
            -
             | 
| 1147 | 
            -
             | 
| 1148 | 
            -
                   | 
| 1149 | 
            -
                   | 
| 1150 | 
            -
             | 
| 1151 | 
            -
             | 
| 1152 | 
            -
             | 
| 1153 | 
            -
             | 
| 1154 | 
            -
                     | 
| 1155 | 
            -
             | 
| 1156 | 
            -
             | 
| 1157 | 
            -
             | 
| 1158 | 
            -
             | 
| 1159 | 
            -
                     | 
| 1160 | 
            -
                     | 
| 1161 | 
            -
                   | 
| 1162 | 
            -
             | 
| 1163 | 
            -
             | 
| 1164 | 
            -
                   | 
| 1165 | 
            -
                     | 
| 1166 | 
            -
             | 
| 1167 | 
            -
             | 
| 1168 | 
            -
             | 
| 1169 | 
            -
             | 
| 1170 | 
            -
             | 
| 1171 | 
            -
             | 
| 1172 | 
            -
             | 
| 1173 | 
            -
                       | 
| 1174 | 
            -
             | 
| 1175 | 
            -
             | 
| 1176 | 
            -
                       | 
| 1177 | 
            -
                       | 
| 1178 | 
            -
             | 
| 1179 | 
            -
             | 
| 1180 | 
            -
                     | 
| 1181 | 
            -
             | 
| 1182 | 
            -
             | 
| 1183 | 
            -
             | 
| 1184 | 
            -
             | 
| 1185 | 
            -
                     | 
| 1186 | 
            -
             | 
| 1187 | 
            -
             | 
| 1188 | 
            -
             | 
| 1189 | 
            -
             | 
| 1190 | 
            -
             | 
| 1191 | 
            -
             | 
| 1192 | 
            -
             | 
| 1193 | 
            -
             | 
| 1194 | 
            -
             | 
| 1195 | 
            -
             | 
| 1196 | 
            -
                     | 
| 1197 | 
            -
             | 
| 1198 | 
            -
             | 
| 1199 | 
            -
             | 
| 1200 | 
            -
             | 
| 1201 | 
            -
             | 
| 1202 | 
            -
             | 
| 1203 | 
            -
             | 
| 1204 | 
            -
             | 
| 1205 | 
            -
             | 
| 1206 | 
            -
            end
         | 
| 1207 | 
            -
             | 
| 1 | 
            +
            # To use the GUI JSON editor, start the edit_json.rb executable script. It
         | 
| 2 | 
            +
            # requires ruby-gtk to be installed.
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'gtk2'
         | 
| 5 | 
            +
            require 'iconv'
         | 
| 6 | 
            +
            require 'json'
         | 
| 7 | 
            +
            require 'rbconfig'
         | 
| 8 | 
            +
            require 'open-uri'
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            module JSON
         | 
| 11 | 
            +
              module Editor
         | 
| 12 | 
            +
                include Gtk
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                # Beginning of the editor window title
         | 
| 15 | 
            +
                TITLE                 = 'JSON Editor'.freeze
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                # Columns constants
         | 
| 18 | 
            +
                ICON_COL, TYPE_COL, CONTENT_COL = 0, 1, 2
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                # JSON primitive types (Containers)
         | 
| 21 | 
            +
                CONTAINER_TYPES = %w[Array Hash].sort
         | 
| 22 | 
            +
                # All JSON primitive types
         | 
| 23 | 
            +
                ALL_TYPES = (%w[TrueClass FalseClass Numeric String NilClass] +
         | 
| 24 | 
            +
                             CONTAINER_TYPES).sort
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                # The Nodes necessary for the tree representation of a JSON document
         | 
| 27 | 
            +
                ALL_NODES = (ALL_TYPES + %w[Key]).sort
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                DEFAULT_DIALOG_KEY_PRESS_HANDLER = lambda do |dialog, event|
         | 
| 30 | 
            +
                  case event.keyval
         | 
| 31 | 
            +
                  when Gdk::Keyval::GDK_Return
         | 
| 32 | 
            +
                    dialog.response Dialog::RESPONSE_ACCEPT
         | 
| 33 | 
            +
                  when Gdk::Keyval::GDK_Escape
         | 
| 34 | 
            +
                    dialog.response Dialog::RESPONSE_REJECT
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                # Returns the Gdk::Pixbuf of the icon named _name_ from the icon cache.
         | 
| 39 | 
            +
                def Editor.fetch_icon(name)
         | 
| 40 | 
            +
                  @icon_cache ||= {}
         | 
| 41 | 
            +
                  unless @icon_cache.key?(name)
         | 
| 42 | 
            +
                    path = File.dirname(__FILE__)
         | 
| 43 | 
            +
                    @icon_cache[name] = Gdk::Pixbuf.new(File.join(path, name + '.xpm'))
         | 
| 44 | 
            +
                  end
         | 
| 45 | 
            +
                 @icon_cache[name]
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                # Opens an error dialog on top of _window_ showing the error message
         | 
| 49 | 
            +
                # _text_.
         | 
| 50 | 
            +
                def Editor.error_dialog(window, text)
         | 
| 51 | 
            +
                  dialog = MessageDialog.new(window, Dialog::MODAL, 
         | 
| 52 | 
            +
                    MessageDialog::ERROR, 
         | 
| 53 | 
            +
                    MessageDialog::BUTTONS_CLOSE, text)
         | 
| 54 | 
            +
                  dialog.show_all
         | 
| 55 | 
            +
                  window.focus = dialog
         | 
| 56 | 
            +
                  dialog.run
         | 
| 57 | 
            +
                rescue TypeError
         | 
| 58 | 
            +
                  dialog = MessageDialog.new(Editor.window, Dialog::MODAL, 
         | 
| 59 | 
            +
                    MessageDialog::ERROR, 
         | 
| 60 | 
            +
                    MessageDialog::BUTTONS_CLOSE, text)
         | 
| 61 | 
            +
                  dialog.show_all
         | 
| 62 | 
            +
                  window.focus = dialog
         | 
| 63 | 
            +
                  dialog.run
         | 
| 64 | 
            +
                ensure
         | 
| 65 | 
            +
                  dialog.destroy if dialog
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                # Opens a yes/no question dialog on top of _window_ showing the error
         | 
| 69 | 
            +
                # message _text_. If yes was answered _true_ is returned, otherwise
         | 
| 70 | 
            +
                # _false_.
         | 
| 71 | 
            +
                def Editor.question_dialog(window, text)
         | 
| 72 | 
            +
                  dialog = MessageDialog.new(window, Dialog::MODAL, 
         | 
| 73 | 
            +
                    MessageDialog::QUESTION, 
         | 
| 74 | 
            +
                    MessageDialog::BUTTONS_YES_NO, text)
         | 
| 75 | 
            +
                  dialog.show_all
         | 
| 76 | 
            +
                  window.focus = dialog
         | 
| 77 | 
            +
                  dialog.run do |response|
         | 
| 78 | 
            +
                    return Gtk::Dialog::RESPONSE_YES === response
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
                ensure
         | 
| 81 | 
            +
                  dialog.destroy if dialog
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                # Convert the tree model starting from Gtk::TreeIter _iter_ into a Ruby
         | 
| 85 | 
            +
                # data structure and return it.
         | 
| 86 | 
            +
                def Editor.model2data(iter)
         | 
| 87 | 
            +
                  return nil if iter.nil?
         | 
| 88 | 
            +
                  case iter.type
         | 
| 89 | 
            +
                  when 'Hash'
         | 
| 90 | 
            +
                    hash = {}
         | 
| 91 | 
            +
                    iter.each { |c| hash[c.content] = Editor.model2data(c.first_child) }
         | 
| 92 | 
            +
                    hash
         | 
| 93 | 
            +
                  when 'Array'
         | 
| 94 | 
            +
                    array = Array.new(iter.n_children)
         | 
| 95 | 
            +
                    iter.each_with_index { |c, i| array[i] = Editor.model2data(c) }
         | 
| 96 | 
            +
                    array
         | 
| 97 | 
            +
                  when 'Key'
         | 
| 98 | 
            +
                    iter.content
         | 
| 99 | 
            +
                  when 'String'
         | 
| 100 | 
            +
                    iter.content
         | 
| 101 | 
            +
                  when 'Numeric'
         | 
| 102 | 
            +
                    content = iter.content
         | 
| 103 | 
            +
                    if /\./.match(content)
         | 
| 104 | 
            +
                      content.to_f
         | 
| 105 | 
            +
                    else
         | 
| 106 | 
            +
                      content.to_i
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
                  when 'TrueClass'
         | 
| 109 | 
            +
                    true
         | 
| 110 | 
            +
                  when 'FalseClass'
         | 
| 111 | 
            +
                    false
         | 
| 112 | 
            +
                  when 'NilClass'
         | 
| 113 | 
            +
                    nil
         | 
| 114 | 
            +
                  else
         | 
| 115 | 
            +
                    fail "Unknown type found in model: #{iter.type}"
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                # Convert the Ruby data structure _data_ into tree model data for Gtk and
         | 
| 120 | 
            +
                # returns the whole model. If the parameter _model_ wasn't given a new
         | 
| 121 | 
            +
                # Gtk::TreeStore is created as the model. The _parent_ parameter specifies
         | 
| 122 | 
            +
                # the parent node (iter, Gtk:TreeIter instance) to which the data is
         | 
| 123 | 
            +
                # appended, alternativeley the result of the yielded block is used as iter.
         | 
| 124 | 
            +
                def Editor.data2model(data, model = nil, parent = nil)
         | 
| 125 | 
            +
                  model ||= TreeStore.new(Gdk::Pixbuf, String, String)
         | 
| 126 | 
            +
                  iter = if block_given?
         | 
| 127 | 
            +
                    yield model
         | 
| 128 | 
            +
                  else
         | 
| 129 | 
            +
                    model.append(parent)
         | 
| 130 | 
            +
                  end
         | 
| 131 | 
            +
                  case data
         | 
| 132 | 
            +
                  when Hash
         | 
| 133 | 
            +
                    iter.type = 'Hash'
         | 
| 134 | 
            +
                    data.sort.each do |key, value|
         | 
| 135 | 
            +
                      pair_iter = model.append(iter)
         | 
| 136 | 
            +
                      pair_iter.type    = 'Key'
         | 
| 137 | 
            +
                      pair_iter.content = key.to_s
         | 
| 138 | 
            +
                      Editor.data2model(value, model, pair_iter)
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
                  when Array
         | 
| 141 | 
            +
                    iter.type = 'Array'
         | 
| 142 | 
            +
                    data.each do |value|
         | 
| 143 | 
            +
                      Editor.data2model(value, model, iter)
         | 
| 144 | 
            +
                    end
         | 
| 145 | 
            +
                  when Numeric
         | 
| 146 | 
            +
                    iter.type = 'Numeric'
         | 
| 147 | 
            +
                    iter.content = data.to_s
         | 
| 148 | 
            +
                  when String, true, false, nil
         | 
| 149 | 
            +
                    iter.type    = data.class.name
         | 
| 150 | 
            +
                    iter.content = data.nil? ? 'null' : data.to_s
         | 
| 151 | 
            +
                  else
         | 
| 152 | 
            +
                    iter.type    = 'String'
         | 
| 153 | 
            +
                    iter.content = data.to_s
         | 
| 154 | 
            +
                  end
         | 
| 155 | 
            +
                  model
         | 
| 156 | 
            +
                end
         | 
| 157 | 
            +
             | 
| 158 | 
            +
                # The Gtk::TreeIter class is reopened and some auxiliary methods are added.
         | 
| 159 | 
            +
                class Gtk::TreeIter
         | 
| 160 | 
            +
                  include Enumerable
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                  # Traverse each of this Gtk::TreeIter instance's children
         | 
| 163 | 
            +
                  # and yield to them.
         | 
| 164 | 
            +
                  def each
         | 
| 165 | 
            +
                    n_children.times { |i| yield nth_child(i) }
         | 
| 166 | 
            +
                  end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                  # Recursively traverse all nodes of this Gtk::TreeIter's subtree
         | 
| 169 | 
            +
                  # (including self) and yield to them.
         | 
| 170 | 
            +
                  def recursive_each(&block)
         | 
| 171 | 
            +
                    yield self
         | 
| 172 | 
            +
                    each do |i|
         | 
| 173 | 
            +
                      i.recursive_each(&block)
         | 
| 174 | 
            +
                    end
         | 
| 175 | 
            +
                  end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                  # Remove the subtree of this Gtk::TreeIter instance from the
         | 
| 178 | 
            +
                  # model _model_.
         | 
| 179 | 
            +
                  def remove_subtree(model)
         | 
| 180 | 
            +
                    while current = first_child
         | 
| 181 | 
            +
                      model.remove(current)
         | 
| 182 | 
            +
                    end
         | 
| 183 | 
            +
                  end
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                  # Returns the type of this node.
         | 
| 186 | 
            +
                  def type
         | 
| 187 | 
            +
                    self[TYPE_COL]
         | 
| 188 | 
            +
                  end
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                  # Sets the type of this node to _value_. This implies setting
         | 
| 191 | 
            +
                  # the respective icon accordingly.
         | 
| 192 | 
            +
                  def type=(value)
         | 
| 193 | 
            +
                    self[TYPE_COL] = value
         | 
| 194 | 
            +
                    self[ICON_COL] = Editor.fetch_icon(value)
         | 
| 195 | 
            +
                  end
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                  # Returns the content of this node.
         | 
| 198 | 
            +
                  def content
         | 
| 199 | 
            +
                    self[CONTENT_COL]
         | 
| 200 | 
            +
                  end
         | 
| 201 | 
            +
             | 
| 202 | 
            +
                  # Sets the content of this node to _value_.
         | 
| 203 | 
            +
                  def content=(value)
         | 
| 204 | 
            +
                    self[CONTENT_COL] = value
         | 
| 205 | 
            +
                  end
         | 
| 206 | 
            +
                end
         | 
| 207 | 
            +
             | 
| 208 | 
            +
                # This module bundles some method, that can be used to create a menu. It
         | 
| 209 | 
            +
                # should be included into the class in question.
         | 
| 210 | 
            +
                module MenuExtension
         | 
| 211 | 
            +
                  include Gtk
         | 
| 212 | 
            +
             | 
| 213 | 
            +
                  # Creates a Menu, that includes MenuExtension. _treeview_ is the
         | 
| 214 | 
            +
                  # Gtk::TreeView, on which it operates.
         | 
| 215 | 
            +
                  def initialize(treeview)
         | 
| 216 | 
            +
                    @treeview = treeview
         | 
| 217 | 
            +
                    @menu = Menu.new
         | 
| 218 | 
            +
                  end
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                  # Returns the Gtk::TreeView of this menu.
         | 
| 221 | 
            +
                  attr_reader :treeview
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                  # Returns the menu.
         | 
| 224 | 
            +
                  attr_reader :menu
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                  # Adds a Gtk::SeparatorMenuItem to this instance's #menu.
         | 
| 227 | 
            +
                  def add_separator
         | 
| 228 | 
            +
                    menu.append SeparatorMenuItem.new
         | 
| 229 | 
            +
                  end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                  # Adds a Gtk::MenuItem to this instance's #menu. _label_ is the label
         | 
| 232 | 
            +
                  # string, _klass_ is the item type, and _callback_ is the procedure, that
         | 
| 233 | 
            +
                  # is called if the _item_ is activated.
         | 
| 234 | 
            +
                  def add_item(label, keyval = nil, klass = MenuItem, &callback)
         | 
| 235 | 
            +
                    label = "#{label} (C-#{keyval.chr})" if keyval
         | 
| 236 | 
            +
                    item = klass.new(label)
         | 
| 237 | 
            +
                    item.signal_connect(:activate, &callback)
         | 
| 238 | 
            +
                    if keyval
         | 
| 239 | 
            +
                      self.signal_connect(:'key-press-event') do |item, event|
         | 
| 240 | 
            +
                        if event.state & Gdk::Window::ModifierType::CONTROL_MASK != 0 and
         | 
| 241 | 
            +
                          event.keyval == keyval
         | 
| 242 | 
            +
                          callback.call item
         | 
| 243 | 
            +
                        end
         | 
| 244 | 
            +
                      end
         | 
| 245 | 
            +
                    end
         | 
| 246 | 
            +
                    menu.append item
         | 
| 247 | 
            +
                    item
         | 
| 248 | 
            +
                  end
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                  # This method should be implemented in subclasses to create the #menu of
         | 
| 251 | 
            +
                  # this instance. It has to be called after an instance of this class is
         | 
| 252 | 
            +
                  # created, to build the menu.
         | 
| 253 | 
            +
                  def create
         | 
| 254 | 
            +
                    raise NotImplementedError
         | 
| 255 | 
            +
                  end
         | 
| 256 | 
            +
             | 
| 257 | 
            +
                  def method_missing(*a, &b)
         | 
| 258 | 
            +
                    treeview.__send__(*a, &b)
         | 
| 259 | 
            +
                  end
         | 
| 260 | 
            +
                end
         | 
| 261 | 
            +
             | 
| 262 | 
            +
                # This class creates the popup menu, that opens when clicking onto the
         | 
| 263 | 
            +
                # treeview.
         | 
| 264 | 
            +
                class PopUpMenu
         | 
| 265 | 
            +
                  include MenuExtension
         | 
| 266 | 
            +
             | 
| 267 | 
            +
                  # Change the type or content of the selected node.
         | 
| 268 | 
            +
                  def change_node(item)
         | 
| 269 | 
            +
                    if current = selection.selected
         | 
| 270 | 
            +
                      parent = current.parent
         | 
| 271 | 
            +
                      old_type, old_content = current.type, current.content
         | 
| 272 | 
            +
                      if ALL_TYPES.include?(old_type)
         | 
| 273 | 
            +
                        @clipboard_data = Editor.model2data(current)
         | 
| 274 | 
            +
                        type, content = ask_for_element(parent, current.type,
         | 
| 275 | 
            +
                          current.content)
         | 
| 276 | 
            +
                        if type
         | 
| 277 | 
            +
                          current.type, current.content = type, content
         | 
| 278 | 
            +
                          current.remove_subtree(model)
         | 
| 279 | 
            +
                          toplevel.display_status("Changed a node in tree.")
         | 
| 280 | 
            +
                          window.change
         | 
| 281 | 
            +
                        end
         | 
| 282 | 
            +
                      else
         | 
| 283 | 
            +
                        toplevel.display_status(
         | 
| 284 | 
            +
                          "Cannot change node of type #{old_type} in tree!")
         | 
| 285 | 
            +
                      end
         | 
| 286 | 
            +
                    end
         | 
| 287 | 
            +
                  end
         | 
| 288 | 
            +
             | 
| 289 | 
            +
                  # Cut the selected node and its subtree, and save it into the
         | 
| 290 | 
            +
                  # clipboard.
         | 
| 291 | 
            +
                  def cut_node(item)
         | 
| 292 | 
            +
                    if current = selection.selected
         | 
| 293 | 
            +
                      if current and current.type == 'Key'
         | 
| 294 | 
            +
                        @clipboard_data = {
         | 
| 295 | 
            +
                          current.content => Editor.model2data(current.first_child)
         | 
| 296 | 
            +
                        }
         | 
| 297 | 
            +
                      else
         | 
| 298 | 
            +
                        @clipboard_data = Editor.model2data(current)
         | 
| 299 | 
            +
                      end
         | 
| 300 | 
            +
                      model.remove(current)
         | 
| 301 | 
            +
                      window.change
         | 
| 302 | 
            +
                      toplevel.display_status("Cut a node from tree.")
         | 
| 303 | 
            +
                    end
         | 
| 304 | 
            +
                  end
         | 
| 305 | 
            +
             | 
| 306 | 
            +
                  # Copy the selected node and its subtree, and save it into the
         | 
| 307 | 
            +
                  # clipboard.
         | 
| 308 | 
            +
                  def copy_node(item)
         | 
| 309 | 
            +
                    if current = selection.selected
         | 
| 310 | 
            +
                      if current and current.type == 'Key'
         | 
| 311 | 
            +
                        @clipboard_data = {
         | 
| 312 | 
            +
                          current.content => Editor.model2data(current.first_child)
         | 
| 313 | 
            +
                        }
         | 
| 314 | 
            +
                      else
         | 
| 315 | 
            +
                        @clipboard_data = Editor.model2data(current)
         | 
| 316 | 
            +
                      end
         | 
| 317 | 
            +
                      window.change
         | 
| 318 | 
            +
                      toplevel.display_status("Copied a node from tree.")
         | 
| 319 | 
            +
                    end
         | 
| 320 | 
            +
                  end
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                  # Paste the data in the clipboard into the selected Array or Hash by
         | 
| 323 | 
            +
                  # appending it.
         | 
| 324 | 
            +
                  def paste_node_appending(item)
         | 
| 325 | 
            +
                    if current = selection.selected
         | 
| 326 | 
            +
                      if @clipboard_data
         | 
| 327 | 
            +
                        case current.type
         | 
| 328 | 
            +
                        when 'Array'
         | 
| 329 | 
            +
                          Editor.data2model(@clipboard_data, model, current)
         | 
| 330 | 
            +
                          expand_collapse(current)
         | 
| 331 | 
            +
                        when 'Hash'
         | 
| 332 | 
            +
                          if @clipboard_data.is_a? Hash
         | 
| 333 | 
            +
                            parent = current.parent
         | 
| 334 | 
            +
                            hash = Editor.model2data(current)
         | 
| 335 | 
            +
                            model.remove(current)
         | 
| 336 | 
            +
                            hash.update(@clipboard_data)
         | 
| 337 | 
            +
                            Editor.data2model(hash, model, parent)
         | 
| 338 | 
            +
                            if parent
         | 
| 339 | 
            +
                              expand_collapse(parent)
         | 
| 340 | 
            +
                            elsif @expanded
         | 
| 341 | 
            +
                              expand_all
         | 
| 342 | 
            +
                            end
         | 
| 343 | 
            +
                            window.change
         | 
| 344 | 
            +
                          else
         | 
| 345 | 
            +
                            toplevel.display_status(
         | 
| 346 | 
            +
                              "Cannot paste non-#{current.type} data into '#{current.type}'!")
         | 
| 347 | 
            +
                          end
         | 
| 348 | 
            +
                        else
         | 
| 349 | 
            +
                          toplevel.display_status(
         | 
| 350 | 
            +
                            "Cannot paste node below '#{current.type}'!")
         | 
| 351 | 
            +
                        end
         | 
| 352 | 
            +
                      else
         | 
| 353 | 
            +
                        toplevel.display_status("Nothing to paste in clipboard!")
         | 
| 354 | 
            +
                      end
         | 
| 355 | 
            +
                    else
         | 
| 356 | 
            +
                        toplevel.display_status("Append a node into the root first!")
         | 
| 357 | 
            +
                    end
         | 
| 358 | 
            +
                  end
         | 
| 359 | 
            +
             | 
| 360 | 
            +
                  # Paste the data in the clipboard into the selected Array inserting it
         | 
| 361 | 
            +
                  # before the selected element.
         | 
| 362 | 
            +
                  def paste_node_inserting_before(item)
         | 
| 363 | 
            +
                    if current = selection.selected
         | 
| 364 | 
            +
                      if @clipboard_data
         | 
| 365 | 
            +
                        parent = current.parent or return
         | 
| 366 | 
            +
                        parent_type = parent.type
         | 
| 367 | 
            +
                        if parent_type == 'Array'
         | 
| 368 | 
            +
                          selected_index = parent.each_with_index do |c, i|
         | 
| 369 | 
            +
                            break i if c == current
         | 
| 370 | 
            +
                          end
         | 
| 371 | 
            +
                          Editor.data2model(@clipboard_data, model, parent) do |m|
         | 
| 372 | 
            +
                            m.insert_before(parent, current)
         | 
| 373 | 
            +
                          end
         | 
| 374 | 
            +
                          expand_collapse(current)
         | 
| 375 | 
            +
                          toplevel.display_status("Inserted an element to " +
         | 
| 376 | 
            +
                            "'#{parent_type}' before index #{selected_index}.")
         | 
| 377 | 
            +
                          window.change
         | 
| 378 | 
            +
                        else
         | 
| 379 | 
            +
                          toplevel.display_status(
         | 
| 380 | 
            +
                            "Cannot insert node below '#{parent_type}'!")
         | 
| 381 | 
            +
                        end
         | 
| 382 | 
            +
                      else
         | 
| 383 | 
            +
                        toplevel.display_status("Nothing to paste in clipboard!")
         | 
| 384 | 
            +
                      end
         | 
| 385 | 
            +
                    else
         | 
| 386 | 
            +
                        toplevel.display_status("Append a node into the root first!")
         | 
| 387 | 
            +
                    end
         | 
| 388 | 
            +
                  end
         | 
| 389 | 
            +
             | 
| 390 | 
            +
                  # Append a new node to the selected Hash or Array.
         | 
| 391 | 
            +
                  def append_new_node(item)
         | 
| 392 | 
            +
                    if parent = selection.selected
         | 
| 393 | 
            +
                      parent_type = parent.type
         | 
| 394 | 
            +
                      case parent_type
         | 
| 395 | 
            +
                      when 'Hash'
         | 
| 396 | 
            +
                        key, type, content = ask_for_hash_pair(parent)
         | 
| 397 | 
            +
                        key or return
         | 
| 398 | 
            +
                        iter = create_node(parent, 'Key', key)
         | 
| 399 | 
            +
                        iter = create_node(iter, type, content)
         | 
| 400 | 
            +
                        toplevel.display_status(
         | 
| 401 | 
            +
                          "Added a (key, value)-pair to '#{parent_type}'.")
         | 
| 402 | 
            +
                        window.change
         | 
| 403 | 
            +
                      when 'Array'
         | 
| 404 | 
            +
                        type, content = ask_for_element(parent)
         | 
| 405 | 
            +
                        type or return
         | 
| 406 | 
            +
                        iter = create_node(parent, type, content)
         | 
| 407 | 
            +
                        window.change
         | 
| 408 | 
            +
                        toplevel.display_status("Appendend an element to '#{parent_type}'.")
         | 
| 409 | 
            +
                      else
         | 
| 410 | 
            +
                        toplevel.display_status("Cannot append to '#{parent_type}'!")
         | 
| 411 | 
            +
                      end
         | 
| 412 | 
            +
                    else
         | 
| 413 | 
            +
                      type, content = ask_for_element
         | 
| 414 | 
            +
                      type or return
         | 
| 415 | 
            +
                      iter = create_node(nil, type, content)
         | 
| 416 | 
            +
                      window.change
         | 
| 417 | 
            +
                    end
         | 
| 418 | 
            +
                  end
         | 
| 419 | 
            +
             | 
| 420 | 
            +
                  # Insert a new node into an Array before the selected element.
         | 
| 421 | 
            +
                  def insert_new_node(item)
         | 
| 422 | 
            +
                    if current = selection.selected
         | 
| 423 | 
            +
                      parent = current.parent or return
         | 
| 424 | 
            +
                      parent_parent = parent.parent
         | 
| 425 | 
            +
                      parent_type = parent.type
         | 
| 426 | 
            +
                      if parent_type == 'Array'
         | 
| 427 | 
            +
                        selected_index = parent.each_with_index do |c, i|
         | 
| 428 | 
            +
                          break i if c == current
         | 
| 429 | 
            +
                        end
         | 
| 430 | 
            +
                        type, content = ask_for_element(parent)
         | 
| 431 | 
            +
                        type or return
         | 
| 432 | 
            +
                        iter = model.insert_before(parent, current)
         | 
| 433 | 
            +
                        iter.type, iter.content = type, content
         | 
| 434 | 
            +
                        toplevel.display_status("Inserted an element to " +
         | 
| 435 | 
            +
                          "'#{parent_type}' before index #{selected_index}.")
         | 
| 436 | 
            +
                        window.change
         | 
| 437 | 
            +
                      else
         | 
| 438 | 
            +
                        toplevel.display_status(
         | 
| 439 | 
            +
                          "Cannot insert node below '#{parent_type}'!")
         | 
| 440 | 
            +
                      end
         | 
| 441 | 
            +
                    else
         | 
| 442 | 
            +
                        toplevel.display_status("Append a node into the root first!")
         | 
| 443 | 
            +
                    end
         | 
| 444 | 
            +
                  end
         | 
| 445 | 
            +
             | 
| 446 | 
            +
                  # Recursively collapse/expand a subtree starting from the selected node.
         | 
| 447 | 
            +
                  def collapse_expand(item)
         | 
| 448 | 
            +
                    if current = selection.selected
         | 
| 449 | 
            +
                      if row_expanded?(current.path)
         | 
| 450 | 
            +
                        collapse_row(current.path)
         | 
| 451 | 
            +
                      else
         | 
| 452 | 
            +
                        expand_row(current.path, true)
         | 
| 453 | 
            +
                      end
         | 
| 454 | 
            +
                    else
         | 
| 455 | 
            +
                        toplevel.display_status("Append a node into the root first!")
         | 
| 456 | 
            +
                    end
         | 
| 457 | 
            +
                  end
         | 
| 458 | 
            +
             | 
| 459 | 
            +
                  # Create the menu.
         | 
| 460 | 
            +
                  def create
         | 
| 461 | 
            +
                    add_item("Change node", ?n, &method(:change_node))
         | 
| 462 | 
            +
                    add_separator
         | 
| 463 | 
            +
                    add_item("Cut node", ?x, &method(:cut_node))
         | 
| 464 | 
            +
                    add_item("Copy node", ?c, &method(:copy_node))
         | 
| 465 | 
            +
                    add_item("Paste node (appending)", ?v, &method(:paste_node_appending))
         | 
| 466 | 
            +
                    add_item("Paste node (inserting before)", ?V,
         | 
| 467 | 
            +
                      &method(:paste_node_inserting_before))
         | 
| 468 | 
            +
                    add_separator
         | 
| 469 | 
            +
                    add_item("Append new node", ?a, &method(:append_new_node))
         | 
| 470 | 
            +
                    add_item("Insert new node before", ?i, &method(:insert_new_node))
         | 
| 471 | 
            +
                    add_separator 
         | 
| 472 | 
            +
                    add_item("Collapse/Expand node (recursively)", ?C,
         | 
| 473 | 
            +
                      &method(:collapse_expand))
         | 
| 474 | 
            +
             | 
| 475 | 
            +
                    menu.show_all
         | 
| 476 | 
            +
                    signal_connect(:button_press_event) do |widget, event|
         | 
| 477 | 
            +
                      if event.kind_of? Gdk::EventButton and event.button == 3
         | 
| 478 | 
            +
                        menu.popup(nil, nil, event.button, event.time)
         | 
| 479 | 
            +
                      end
         | 
| 480 | 
            +
                    end
         | 
| 481 | 
            +
                    signal_connect(:popup_menu) do
         | 
| 482 | 
            +
                      menu.popup(nil, nil, 0, Gdk::Event::CURRENT_TIME)
         | 
| 483 | 
            +
                    end
         | 
| 484 | 
            +
                  end
         | 
| 485 | 
            +
                end
         | 
| 486 | 
            +
             | 
| 487 | 
            +
                # This class creates the File pulldown menu.
         | 
| 488 | 
            +
                class FileMenu
         | 
| 489 | 
            +
                  include MenuExtension
         | 
| 490 | 
            +
             | 
| 491 | 
            +
                  # Clear the model and filename, but ask to save the JSON document, if
         | 
| 492 | 
            +
                  # unsaved changes have occured.
         | 
| 493 | 
            +
                  def new(item)
         | 
| 494 | 
            +
                    window.clear
         | 
| 495 | 
            +
                  end
         | 
| 496 | 
            +
             | 
| 497 | 
            +
                  # Open a file and load it into the editor. Ask to save the JSON document
         | 
| 498 | 
            +
                  # first, if unsaved changes have occured.
         | 
| 499 | 
            +
                  def open(item)
         | 
| 500 | 
            +
                    window.file_open
         | 
| 501 | 
            +
                  end
         | 
| 502 | 
            +
             | 
| 503 | 
            +
                  def open_location(item)
         | 
| 504 | 
            +
                    window.location_open
         | 
| 505 | 
            +
                  end
         | 
| 506 | 
            +
             | 
| 507 | 
            +
                  # Revert the current JSON document in the editor to the saved version.
         | 
| 508 | 
            +
                  def revert(item)
         | 
| 509 | 
            +
                    window.instance_eval do
         | 
| 510 | 
            +
                      @filename and file_open(@filename) 
         | 
| 511 | 
            +
                    end
         | 
| 512 | 
            +
                  end
         | 
| 513 | 
            +
             | 
| 514 | 
            +
                  # Save the current JSON document.
         | 
| 515 | 
            +
                  def save(item)
         | 
| 516 | 
            +
                    window.file_save
         | 
| 517 | 
            +
                  end
         | 
| 518 | 
            +
             | 
| 519 | 
            +
                  # Save the current JSON document under the given filename.
         | 
| 520 | 
            +
                  def save_as(item)
         | 
| 521 | 
            +
                    window.file_save_as
         | 
| 522 | 
            +
                  end
         | 
| 523 | 
            +
             | 
| 524 | 
            +
                  # Quit the editor, after asking to save any unsaved changes first.
         | 
| 525 | 
            +
                  def quit(item)
         | 
| 526 | 
            +
                    window.quit
         | 
| 527 | 
            +
                  end
         | 
| 528 | 
            +
             | 
| 529 | 
            +
                  # Create the menu.
         | 
| 530 | 
            +
                  def create
         | 
| 531 | 
            +
                    title = MenuItem.new('File')
         | 
| 532 | 
            +
                    title.submenu = menu
         | 
| 533 | 
            +
                    add_item('New', &method(:new))
         | 
| 534 | 
            +
                    add_item('Open', ?o, &method(:open))
         | 
| 535 | 
            +
                    add_item('Open location', ?l, &method(:open_location))
         | 
| 536 | 
            +
                    add_item('Revert', &method(:revert))
         | 
| 537 | 
            +
                    add_separator
         | 
| 538 | 
            +
                    add_item('Save', ?s, &method(:save))
         | 
| 539 | 
            +
                    add_item('Save As', ?S, &method(:save_as))
         | 
| 540 | 
            +
                    add_separator
         | 
| 541 | 
            +
                    add_item('Quit', ?q, &method(:quit))
         | 
| 542 | 
            +
                    title
         | 
| 543 | 
            +
                  end
         | 
| 544 | 
            +
                end
         | 
| 545 | 
            +
             | 
| 546 | 
            +
                # This class creates the Edit pulldown menu.
         | 
| 547 | 
            +
                class EditMenu
         | 
| 548 | 
            +
                  include MenuExtension
         | 
| 549 | 
            +
             | 
| 550 | 
            +
                  # Find a string in all nodes' contents and select the found node in the
         | 
| 551 | 
            +
                  # treeview.
         | 
| 552 | 
            +
                  def find(item)
         | 
| 553 | 
            +
                    search = ask_for_find_term or return
         | 
| 554 | 
            +
                    begin
         | 
| 555 | 
            +
                      @search = Regexp.new(search)
         | 
| 556 | 
            +
                    rescue => e
         | 
| 557 | 
            +
                      Editor.error_dialog(self, "Evaluation of regex /#{search}/ failed: #{e}!")
         | 
| 558 | 
            +
                      return
         | 
| 559 | 
            +
                    end
         | 
| 560 | 
            +
                    iter = model.get_iter('0')
         | 
| 561 | 
            +
                    iter.recursive_each do |i|
         | 
| 562 | 
            +
                      if @iter
         | 
| 563 | 
            +
                        if @iter != i
         | 
| 564 | 
            +
                          next
         | 
| 565 | 
            +
                        else
         | 
| 566 | 
            +
                          @iter = nil
         | 
| 567 | 
            +
                          next
         | 
| 568 | 
            +
                        end
         | 
| 569 | 
            +
                      elsif @search.match(i[CONTENT_COL])
         | 
| 570 | 
            +
                         set_cursor(i.path, nil, false)
         | 
| 571 | 
            +
                         @iter = i
         | 
| 572 | 
            +
                         break
         | 
| 573 | 
            +
                      end
         | 
| 574 | 
            +
                    end
         | 
| 575 | 
            +
                  end
         | 
| 576 | 
            +
             | 
| 577 | 
            +
                  # Repeat the last search given by #find.
         | 
| 578 | 
            +
                  def find_again(item)
         | 
| 579 | 
            +
                    @search or return
         | 
| 580 | 
            +
                    iter = model.get_iter('0')
         | 
| 581 | 
            +
                    iter.recursive_each do |i|
         | 
| 582 | 
            +
                      if @iter
         | 
| 583 | 
            +
                        if @iter != i
         | 
| 584 | 
            +
                          next
         | 
| 585 | 
            +
                        else
         | 
| 586 | 
            +
                          @iter = nil
         | 
| 587 | 
            +
                          next
         | 
| 588 | 
            +
                        end
         | 
| 589 | 
            +
                      elsif @search.match(i[CONTENT_COL])
         | 
| 590 | 
            +
                         set_cursor(i.path, nil, false)
         | 
| 591 | 
            +
                         @iter = i
         | 
| 592 | 
            +
                         break
         | 
| 593 | 
            +
                      end
         | 
| 594 | 
            +
                    end
         | 
| 595 | 
            +
                  end
         | 
| 596 | 
            +
             | 
| 597 | 
            +
                  # Sort (Reverse sort) all elements of the selected array by the given
         | 
| 598 | 
            +
                  # expression. _x_ is the element in question.
         | 
| 599 | 
            +
                  def sort(item)
         | 
| 600 | 
            +
                    if current = selection.selected
         | 
| 601 | 
            +
                      if current.type == 'Array'
         | 
| 602 | 
            +
                        parent = current.parent
         | 
| 603 | 
            +
                        ary = Editor.model2data(current)
         | 
| 604 | 
            +
                        order, reverse = ask_for_order
         | 
| 605 | 
            +
                        order or return
         | 
| 606 | 
            +
                        begin
         | 
| 607 | 
            +
                          block = eval "lambda { |x| #{order} }"
         | 
| 608 | 
            +
                          if reverse
         | 
| 609 | 
            +
                            ary.sort! { |a,b| block[b] <=> block[a] }
         | 
| 610 | 
            +
                          else
         | 
| 611 | 
            +
                            ary.sort! { |a,b| block[a] <=> block[b] }
         | 
| 612 | 
            +
                          end
         | 
| 613 | 
            +
                        rescue => e
         | 
| 614 | 
            +
                          Editor.error_dialog(self, "Failed to sort Array with #{order}: #{e}!")
         | 
| 615 | 
            +
                        else
         | 
| 616 | 
            +
                          Editor.data2model(ary, model, parent) do |m|
         | 
| 617 | 
            +
                            m.insert_before(parent, current)
         | 
| 618 | 
            +
                          end
         | 
| 619 | 
            +
                          model.remove(current)
         | 
| 620 | 
            +
                          expand_collapse(parent)
         | 
| 621 | 
            +
                          window.change
         | 
| 622 | 
            +
                          toplevel.display_status("Array has been sorted.")
         | 
| 623 | 
            +
                        end
         | 
| 624 | 
            +
                      else
         | 
| 625 | 
            +
                        toplevel.display_status("Only Array nodes can be sorted!")
         | 
| 626 | 
            +
                      end
         | 
| 627 | 
            +
                    else
         | 
| 628 | 
            +
                        toplevel.display_status("Select an Array to sort first!")
         | 
| 629 | 
            +
                    end
         | 
| 630 | 
            +
                  end
         | 
| 631 | 
            +
             | 
| 632 | 
            +
                  # Create the menu.
         | 
| 633 | 
            +
                  def create
         | 
| 634 | 
            +
                    title = MenuItem.new('Edit')
         | 
| 635 | 
            +
                    title.submenu = menu
         | 
| 636 | 
            +
                    add_item('Find', ?f, &method(:find))
         | 
| 637 | 
            +
                    add_item('Find Again', ?g, &method(:find_again))
         | 
| 638 | 
            +
                    add_separator
         | 
| 639 | 
            +
                    add_item('Sort', ?S, &method(:sort))
         | 
| 640 | 
            +
                    title
         | 
| 641 | 
            +
                  end
         | 
| 642 | 
            +
                end
         | 
| 643 | 
            +
             | 
| 644 | 
            +
                class OptionsMenu
         | 
| 645 | 
            +
                  include MenuExtension
         | 
| 646 | 
            +
             | 
| 647 | 
            +
                  # Collapse/Expand all nodes by default.
         | 
| 648 | 
            +
                  def collapsed_nodes(item)
         | 
| 649 | 
            +
                    if expanded
         | 
| 650 | 
            +
                      self.expanded = false
         | 
| 651 | 
            +
                      collapse_all
         | 
| 652 | 
            +
                    else
         | 
| 653 | 
            +
                      self.expanded = true
         | 
| 654 | 
            +
                      expand_all 
         | 
| 655 | 
            +
                    end
         | 
| 656 | 
            +
                  end
         | 
| 657 | 
            +
             | 
| 658 | 
            +
                  # Toggle pretty saving mode on/off.
         | 
| 659 | 
            +
                  def pretty_saving(item)
         | 
| 660 | 
            +
                    @pretty_item.toggled
         | 
| 661 | 
            +
                    window.change
         | 
| 662 | 
            +
                  end
         | 
| 663 | 
            +
             | 
| 664 | 
            +
                  attr_reader :pretty_item
         | 
| 665 | 
            +
             | 
| 666 | 
            +
                  # Create the menu.
         | 
| 667 | 
            +
                  def create
         | 
| 668 | 
            +
                    title = MenuItem.new('Options')
         | 
| 669 | 
            +
                    title.submenu = menu
         | 
| 670 | 
            +
                    add_item('Collapsed nodes', nil, CheckMenuItem, &method(:collapsed_nodes))
         | 
| 671 | 
            +
                    @pretty_item = add_item('Pretty saving', nil, CheckMenuItem,
         | 
| 672 | 
            +
                      &method(:pretty_saving))
         | 
| 673 | 
            +
                    @pretty_item.active = true
         | 
| 674 | 
            +
                    window.unchange
         | 
| 675 | 
            +
                    title
         | 
| 676 | 
            +
                  end
         | 
| 677 | 
            +
                end
         | 
| 678 | 
            +
             | 
| 679 | 
            +
                # This class inherits from Gtk::TreeView, to configure it and to add a lot
         | 
| 680 | 
            +
                # of behaviour to it.
         | 
| 681 | 
            +
                class JSONTreeView < Gtk::TreeView
         | 
| 682 | 
            +
                  include Gtk
         | 
| 683 | 
            +
             | 
| 684 | 
            +
                  # Creates a JSONTreeView instance, the parameter _window_ is
         | 
| 685 | 
            +
                  # a MainWindow instance and used for self delegation.
         | 
| 686 | 
            +
                  def initialize(window)
         | 
| 687 | 
            +
                    @window = window
         | 
| 688 | 
            +
                    super(TreeStore.new(Gdk::Pixbuf, String, String))
         | 
| 689 | 
            +
                    self.selection.mode = SELECTION_BROWSE
         | 
| 690 | 
            +
             | 
| 691 | 
            +
                    @expanded = false
         | 
| 692 | 
            +
                    self.headers_visible = false
         | 
| 693 | 
            +
                    add_columns
         | 
| 694 | 
            +
                    add_popup_menu
         | 
| 695 | 
            +
                  end
         | 
| 696 | 
            +
             | 
| 697 | 
            +
                  # Returns the MainWindow instance of this JSONTreeView.
         | 
| 698 | 
            +
                  attr_reader :window
         | 
| 699 | 
            +
             | 
| 700 | 
            +
                  # Returns true, if nodes are autoexpanding, false otherwise.
         | 
| 701 | 
            +
                  attr_accessor :expanded
         | 
| 702 | 
            +
             | 
| 703 | 
            +
                  private
         | 
| 704 | 
            +
             | 
| 705 | 
            +
                  def add_columns
         | 
| 706 | 
            +
                    cell = CellRendererPixbuf.new
         | 
| 707 | 
            +
                    column = TreeViewColumn.new('Icon', cell,
         | 
| 708 | 
            +
                      'pixbuf'      => ICON_COL
         | 
| 709 | 
            +
                    )
         | 
| 710 | 
            +
                    append_column(column)
         | 
| 711 | 
            +
             | 
| 712 | 
            +
                    cell = CellRendererText.new
         | 
| 713 | 
            +
                    column = TreeViewColumn.new('Type', cell,
         | 
| 714 | 
            +
                      'text'      => TYPE_COL
         | 
| 715 | 
            +
                    )
         | 
| 716 | 
            +
                    append_column(column)
         | 
| 717 | 
            +
             | 
| 718 | 
            +
                    cell = CellRendererText.new
         | 
| 719 | 
            +
                    cell.editable = true
         | 
| 720 | 
            +
                    column = TreeViewColumn.new('Content', cell,
         | 
| 721 | 
            +
                      'text'       => CONTENT_COL
         | 
| 722 | 
            +
                    )
         | 
| 723 | 
            +
                    cell.signal_connect(:edited, &method(:cell_edited))
         | 
| 724 | 
            +
                    append_column(column)
         | 
| 725 | 
            +
                  end
         | 
| 726 | 
            +
             | 
| 727 | 
            +
                  def unify_key(iter, key)
         | 
| 728 | 
            +
                    return unless iter.type == 'Key'
         | 
| 729 | 
            +
                    parent = iter.parent
         | 
| 730 | 
            +
                    if parent.any? { |c| c != iter and c.content == key }
         | 
| 731 | 
            +
                      old_key = key
         | 
| 732 | 
            +
                      i = 0
         | 
| 733 | 
            +
                      begin
         | 
| 734 | 
            +
                        key = sprintf("%s.%d", old_key, i += 1)
         | 
| 735 | 
            +
                      end while parent.any? { |c| c != iter and c.content == key }
         | 
| 736 | 
            +
                    end
         | 
| 737 | 
            +
                    iter.content = key
         | 
| 738 | 
            +
                  end
         | 
| 739 | 
            +
             | 
| 740 | 
            +
                  def cell_edited(cell, path, value)
         | 
| 741 | 
            +
                    iter = model.get_iter(path)
         | 
| 742 | 
            +
                    case iter.type
         | 
| 743 | 
            +
                    when 'Key'
         | 
| 744 | 
            +
                      unify_key(iter, value)
         | 
| 745 | 
            +
                      toplevel.display_status('Key has been changed.')
         | 
| 746 | 
            +
                    when 'FalseClass'
         | 
| 747 | 
            +
                      value.downcase!
         | 
| 748 | 
            +
                      if value == 'true'
         | 
| 749 | 
            +
                        iter.type, iter.content = 'TrueClass', 'true'
         | 
| 750 | 
            +
                      end
         | 
| 751 | 
            +
                    when 'TrueClass'
         | 
| 752 | 
            +
                      value.downcase!
         | 
| 753 | 
            +
                      if value == 'false'
         | 
| 754 | 
            +
                        iter.type, iter.content = 'FalseClass', 'false'
         | 
| 755 | 
            +
                      end
         | 
| 756 | 
            +
                    when 'Numeric'
         | 
| 757 | 
            +
                      iter.content = (Integer(value) rescue Float(value) rescue 0).to_s
         | 
| 758 | 
            +
                    when 'String'
         | 
| 759 | 
            +
                      iter.content = value
         | 
| 760 | 
            +
                    when 'Hash', 'Array'
         | 
| 761 | 
            +
                      return
         | 
| 762 | 
            +
                    else
         | 
| 763 | 
            +
                      fail "Unknown type found in model: #{iter.type}"
         | 
| 764 | 
            +
                    end
         | 
| 765 | 
            +
                    window.change
         | 
| 766 | 
            +
                  end
         | 
| 767 | 
            +
             | 
| 768 | 
            +
                  def configure_value(value, type)
         | 
| 769 | 
            +
                    value.editable = false
         | 
| 770 | 
            +
                    case type
         | 
| 771 | 
            +
                    when 'Array', 'Hash'
         | 
| 772 | 
            +
                      value.text = ''
         | 
| 773 | 
            +
                    when 'TrueClass'
         | 
| 774 | 
            +
                      value.text = 'true'
         | 
| 775 | 
            +
                    when 'FalseClass'
         | 
| 776 | 
            +
                      value.text = 'false'
         | 
| 777 | 
            +
                    when 'NilClass'
         | 
| 778 | 
            +
                      value.text = 'null'
         | 
| 779 | 
            +
                    when 'Numeric', 'String'
         | 
| 780 | 
            +
                      value.text ||= ''
         | 
| 781 | 
            +
                      value.editable = true
         | 
| 782 | 
            +
                    else
         | 
| 783 | 
            +
                      raise ArgumentError, "unknown type '#{type}' encountered"
         | 
| 784 | 
            +
                    end
         | 
| 785 | 
            +
                  end
         | 
| 786 | 
            +
             | 
| 787 | 
            +
                  def add_popup_menu
         | 
| 788 | 
            +
                    menu = PopUpMenu.new(self)
         | 
| 789 | 
            +
                    menu.create
         | 
| 790 | 
            +
                  end
         | 
| 791 | 
            +
             | 
| 792 | 
            +
                  public
         | 
| 793 | 
            +
             | 
| 794 | 
            +
                  # Create a _type_ node with content _content_, and add it to _parent_
         | 
| 795 | 
            +
                  # in the model. If _parent_ is nil, create a new model and put it into
         | 
| 796 | 
            +
                  # the editor treeview.
         | 
| 797 | 
            +
                  def create_node(parent, type, content)
         | 
| 798 | 
            +
                    iter = if parent
         | 
| 799 | 
            +
                      model.append(parent)
         | 
| 800 | 
            +
                    else
         | 
| 801 | 
            +
                      new_model = Editor.data2model(nil)
         | 
| 802 | 
            +
                      toplevel.view_new_model(new_model)
         | 
| 803 | 
            +
                      new_model.iter_first
         | 
| 804 | 
            +
                    end
         | 
| 805 | 
            +
                    iter.type, iter.content = type, content
         | 
| 806 | 
            +
                    expand_collapse(parent) if parent
         | 
| 807 | 
            +
                    iter
         | 
| 808 | 
            +
                  end
         | 
| 809 | 
            +
             | 
| 810 | 
            +
                  # Ask for a hash key, value pair to be added to the Hash node _parent_.
         | 
| 811 | 
            +
                  def ask_for_hash_pair(parent)
         | 
| 812 | 
            +
                    key_input = type_input = value_input = nil
         | 
| 813 | 
            +
             | 
| 814 | 
            +
                    dialog = Dialog.new("New (key, value) pair for Hash", nil, nil,
         | 
| 815 | 
            +
                      [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
         | 
| 816 | 
            +
                      [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
         | 
| 817 | 
            +
                    )
         | 
| 818 | 
            +
             | 
| 819 | 
            +
                    hbox = HBox.new(false, 5)
         | 
| 820 | 
            +
                    hbox.pack_start(Label.new("Key:"))
         | 
| 821 | 
            +
                    hbox.pack_start(key_input = Entry.new)
         | 
| 822 | 
            +
                    key_input.text = @key || ''
         | 
| 823 | 
            +
                    dialog.vbox.add(hbox)
         | 
| 824 | 
            +
                    key_input.signal_connect(:activate) do
         | 
| 825 | 
            +
                      if parent.any? { |c| c.content == key_input.text }
         | 
| 826 | 
            +
                        toplevel.display_status('Key already exists in Hash!')
         | 
| 827 | 
            +
                        key_input.text = ''
         | 
| 828 | 
            +
                      else
         | 
| 829 | 
            +
                        toplevel.display_status('Key has been changed.')
         | 
| 830 | 
            +
                      end
         | 
| 831 | 
            +
                    end
         | 
| 832 | 
            +
             | 
| 833 | 
            +
                    hbox = HBox.new(false, 5)
         | 
| 834 | 
            +
                    hbox.add(Label.new("Type:"))
         | 
| 835 | 
            +
                    hbox.pack_start(type_input = ComboBox.new(true))
         | 
| 836 | 
            +
                    ALL_TYPES.each { |t| type_input.append_text(t) }
         | 
| 837 | 
            +
                    type_input.active = @type || 0
         | 
| 838 | 
            +
                    dialog.vbox.add(hbox)
         | 
| 839 | 
            +
             | 
| 840 | 
            +
                    type_input.signal_connect(:changed) do
         | 
| 841 | 
            +
                      value_input.editable = false
         | 
| 842 | 
            +
                      case ALL_TYPES[type_input.active]
         | 
| 843 | 
            +
                      when 'Array', 'Hash'
         | 
| 844 | 
            +
                        value_input.text = ''
         | 
| 845 | 
            +
                      when 'TrueClass'
         | 
| 846 | 
            +
                        value_input.text = 'true'
         | 
| 847 | 
            +
                      when 'FalseClass'
         | 
| 848 | 
            +
                        value_input.text = 'false'
         | 
| 849 | 
            +
                      when 'NilClass'
         | 
| 850 | 
            +
                        value_input.text = 'null'
         | 
| 851 | 
            +
                      else
         | 
| 852 | 
            +
                        value_input.text = ''
         | 
| 853 | 
            +
                        value_input.editable = true
         | 
| 854 | 
            +
                      end
         | 
| 855 | 
            +
                    end
         | 
| 856 | 
            +
             | 
| 857 | 
            +
                    hbox = HBox.new(false, 5)
         | 
| 858 | 
            +
                    hbox.add(Label.new("Value:"))
         | 
| 859 | 
            +
                    hbox.pack_start(value_input = Entry.new)
         | 
| 860 | 
            +
                    value_input.text = @value || ''
         | 
| 861 | 
            +
                    dialog.vbox.add(hbox)
         | 
| 862 | 
            +
             | 
| 863 | 
            +
                    dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
         | 
| 864 | 
            +
                    dialog.show_all
         | 
| 865 | 
            +
                    self.focus = dialog
         | 
| 866 | 
            +
                    dialog.run do |response| 
         | 
| 867 | 
            +
                      if response == Dialog::RESPONSE_ACCEPT
         | 
| 868 | 
            +
                        @key = key_input.text
         | 
| 869 | 
            +
                        type = ALL_TYPES[@type = type_input.active]
         | 
| 870 | 
            +
                        content = value_input.text
         | 
| 871 | 
            +
                        return @key, type, content
         | 
| 872 | 
            +
                      end
         | 
| 873 | 
            +
                    end
         | 
| 874 | 
            +
                    return
         | 
| 875 | 
            +
                  ensure
         | 
| 876 | 
            +
                    dialog.destroy
         | 
| 877 | 
            +
                  end
         | 
| 878 | 
            +
             | 
| 879 | 
            +
                  # Ask for an element to be appended _parent_.
         | 
| 880 | 
            +
                  def ask_for_element(parent = nil, default_type = nil, value_text = @content)
         | 
| 881 | 
            +
                    type_input = value_input = nil
         | 
| 882 | 
            +
             | 
| 883 | 
            +
                    dialog = Dialog.new(
         | 
| 884 | 
            +
                      "New element into #{parent ? parent.type : 'root'}",
         | 
| 885 | 
            +
                      nil, nil,
         | 
| 886 | 
            +
                      [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
         | 
| 887 | 
            +
                      [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
         | 
| 888 | 
            +
                    )
         | 
| 889 | 
            +
                    hbox = HBox.new(false, 5)
         | 
| 890 | 
            +
                    hbox.add(Label.new("Type:"))
         | 
| 891 | 
            +
                    hbox.pack_start(type_input = ComboBox.new(true))
         | 
| 892 | 
            +
                    default_active = 0
         | 
| 893 | 
            +
                    types = parent ? ALL_TYPES : CONTAINER_TYPES
         | 
| 894 | 
            +
                    types.each_with_index do |t, i|
         | 
| 895 | 
            +
                      type_input.append_text(t)
         | 
| 896 | 
            +
                      if t == default_type
         | 
| 897 | 
            +
                        default_active = i
         | 
| 898 | 
            +
                      end
         | 
| 899 | 
            +
                    end
         | 
| 900 | 
            +
                    type_input.active = default_active
         | 
| 901 | 
            +
                    dialog.vbox.add(hbox)
         | 
| 902 | 
            +
                    type_input.signal_connect(:changed) do
         | 
| 903 | 
            +
                      configure_value(value_input, types[type_input.active])
         | 
| 904 | 
            +
                    end
         | 
| 905 | 
            +
             | 
| 906 | 
            +
                    hbox = HBox.new(false, 5)
         | 
| 907 | 
            +
                    hbox.add(Label.new("Value:"))
         | 
| 908 | 
            +
                    hbox.pack_start(value_input = Entry.new)
         | 
| 909 | 
            +
                    value_input.text = value_text if value_text
         | 
| 910 | 
            +
                    configure_value(value_input, types[type_input.active])
         | 
| 911 | 
            +
             | 
| 912 | 
            +
                    dialog.vbox.add(hbox)
         | 
| 913 | 
            +
             | 
| 914 | 
            +
                    dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
         | 
| 915 | 
            +
                    dialog.show_all
         | 
| 916 | 
            +
                    self.focus = dialog
         | 
| 917 | 
            +
                    dialog.run do |response| 
         | 
| 918 | 
            +
                      if response == Dialog::RESPONSE_ACCEPT
         | 
| 919 | 
            +
                        type = types[type_input.active]
         | 
| 920 | 
            +
                        @content = case type
         | 
| 921 | 
            +
                        when 'Numeric'
         | 
| 922 | 
            +
                          Integer(value_input.text) rescue Float(value_input.text) rescue 0
         | 
| 923 | 
            +
                        else
         | 
| 924 | 
            +
                          value_input.text
         | 
| 925 | 
            +
                        end.to_s
         | 
| 926 | 
            +
                        return type, @content
         | 
| 927 | 
            +
                      end
         | 
| 928 | 
            +
                    end
         | 
| 929 | 
            +
                    return
         | 
| 930 | 
            +
                  ensure
         | 
| 931 | 
            +
                    dialog.destroy if dialog
         | 
| 932 | 
            +
                  end
         | 
| 933 | 
            +
             | 
| 934 | 
            +
                  # Ask for an order criteria for sorting, using _x_ for the element in
         | 
| 935 | 
            +
                  # question. Returns the order criterium, and true/false for reverse
         | 
| 936 | 
            +
                  # sorting.
         | 
| 937 | 
            +
                  def ask_for_order
         | 
| 938 | 
            +
                    dialog = Dialog.new(
         | 
| 939 | 
            +
                      "Give an order criterium for 'x'.",
         | 
| 940 | 
            +
                      nil, nil,
         | 
| 941 | 
            +
                      [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
         | 
| 942 | 
            +
                      [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
         | 
| 943 | 
            +
                    )
         | 
| 944 | 
            +
                    hbox = HBox.new(false, 5)
         | 
| 945 | 
            +
             | 
| 946 | 
            +
                    hbox.add(Label.new("Order:"))
         | 
| 947 | 
            +
                    hbox.pack_start(order_input = Entry.new)
         | 
| 948 | 
            +
                    order_input.text = @order || 'x'
         | 
| 949 | 
            +
             | 
| 950 | 
            +
                    hbox.pack_start(reverse_checkbox = CheckButton.new('Reverse'))
         | 
| 951 | 
            +
             | 
| 952 | 
            +
                    dialog.vbox.add(hbox)
         | 
| 953 | 
            +
             | 
| 954 | 
            +
                    dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
         | 
| 955 | 
            +
                    dialog.show_all
         | 
| 956 | 
            +
                    self.focus = dialog
         | 
| 957 | 
            +
                    dialog.run do |response| 
         | 
| 958 | 
            +
                      if response == Dialog::RESPONSE_ACCEPT
         | 
| 959 | 
            +
                        return @order = order_input.text, reverse_checkbox.active?
         | 
| 960 | 
            +
                      end
         | 
| 961 | 
            +
                    end
         | 
| 962 | 
            +
                    return
         | 
| 963 | 
            +
                  ensure
         | 
| 964 | 
            +
                    dialog.destroy if dialog
         | 
| 965 | 
            +
                  end
         | 
| 966 | 
            +
             | 
| 967 | 
            +
                  # Ask for a find term to search for in the tree. Returns the term as a
         | 
| 968 | 
            +
                  # string.
         | 
| 969 | 
            +
                  def ask_for_find_term
         | 
| 970 | 
            +
                    dialog = Dialog.new(
         | 
| 971 | 
            +
                      "Find a node matching regex in tree.",
         | 
| 972 | 
            +
                      nil, nil,
         | 
| 973 | 
            +
                      [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
         | 
| 974 | 
            +
                      [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
         | 
| 975 | 
            +
                    )
         | 
| 976 | 
            +
                    hbox = HBox.new(false, 5)
         | 
| 977 | 
            +
             | 
| 978 | 
            +
                    hbox.add(Label.new("Regex:"))
         | 
| 979 | 
            +
                    hbox.pack_start(regex_input = Entry.new)
         | 
| 980 | 
            +
                    regex_input.text = @regex || ''
         | 
| 981 | 
            +
             | 
| 982 | 
            +
                    dialog.vbox.add(hbox)
         | 
| 983 | 
            +
             | 
| 984 | 
            +
                    dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
         | 
| 985 | 
            +
                    dialog.show_all
         | 
| 986 | 
            +
                    self.focus = dialog
         | 
| 987 | 
            +
                    dialog.run do |response| 
         | 
| 988 | 
            +
                      if response == Dialog::RESPONSE_ACCEPT
         | 
| 989 | 
            +
                        return @regex = regex_input.text
         | 
| 990 | 
            +
                      end
         | 
| 991 | 
            +
                    end
         | 
| 992 | 
            +
                    return
         | 
| 993 | 
            +
                  ensure
         | 
| 994 | 
            +
                    dialog.destroy if dialog
         | 
| 995 | 
            +
                  end
         | 
| 996 | 
            +
             | 
| 997 | 
            +
                  # Expand or collapse row pointed to by _iter_ according
         | 
| 998 | 
            +
                  # to the #expanded attribute.
         | 
| 999 | 
            +
                  def expand_collapse(iter)
         | 
| 1000 | 
            +
                    if expanded
         | 
| 1001 | 
            +
                      expand_row(iter.path, true)
         | 
| 1002 | 
            +
                    else
         | 
| 1003 | 
            +
                      collapse_row(iter.path)
         | 
| 1004 | 
            +
                    end
         | 
| 1005 | 
            +
                  end
         | 
| 1006 | 
            +
                end
         | 
| 1007 | 
            +
             | 
| 1008 | 
            +
                # The editor main window
         | 
| 1009 | 
            +
                class MainWindow < Gtk::Window
         | 
| 1010 | 
            +
                  include Gtk
         | 
| 1011 | 
            +
             | 
| 1012 | 
            +
                  def initialize(encoding)
         | 
| 1013 | 
            +
                    @changed  = false
         | 
| 1014 | 
            +
                    @encoding = encoding
         | 
| 1015 | 
            +
                    super(TOPLEVEL)
         | 
| 1016 | 
            +
                    display_title
         | 
| 1017 | 
            +
                    set_default_size(800, 600)
         | 
| 1018 | 
            +
                    signal_connect(:delete_event) { quit }
         | 
| 1019 | 
            +
             | 
| 1020 | 
            +
                    vbox = VBox.new(false, 0)
         | 
| 1021 | 
            +
                    add(vbox)
         | 
| 1022 | 
            +
                    #vbox.border_width = 0
         | 
| 1023 | 
            +
             | 
| 1024 | 
            +
                    @treeview = JSONTreeView.new(self)
         | 
| 1025 | 
            +
                    @treeview.signal_connect(:'cursor-changed') do
         | 
| 1026 | 
            +
                      display_status('')
         | 
| 1027 | 
            +
                    end
         | 
| 1028 | 
            +
             | 
| 1029 | 
            +
                    menu_bar = create_menu_bar
         | 
| 1030 | 
            +
                    vbox.pack_start(menu_bar, false, false, 0)
         | 
| 1031 | 
            +
             | 
| 1032 | 
            +
                    sw = ScrolledWindow.new(nil, nil)
         | 
| 1033 | 
            +
                    sw.shadow_type = SHADOW_ETCHED_IN
         | 
| 1034 | 
            +
                    sw.set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
         | 
| 1035 | 
            +
                    vbox.pack_start(sw, true, true, 0)
         | 
| 1036 | 
            +
                    sw.add(@treeview)
         | 
| 1037 | 
            +
             | 
| 1038 | 
            +
                    @status_bar = Statusbar.new
         | 
| 1039 | 
            +
                    vbox.pack_start(@status_bar, false, false, 0)
         | 
| 1040 | 
            +
             | 
| 1041 | 
            +
                    @filename ||= nil
         | 
| 1042 | 
            +
                    if @filename
         | 
| 1043 | 
            +
                      data = read_data(@filename)
         | 
| 1044 | 
            +
                      view_new_model Editor.data2model(data)
         | 
| 1045 | 
            +
                    end
         | 
| 1046 | 
            +
                  end
         | 
| 1047 | 
            +
             | 
| 1048 | 
            +
                  # Creates the menu bar with the pulldown menus and returns it.
         | 
| 1049 | 
            +
                  def create_menu_bar
         | 
| 1050 | 
            +
                    menu_bar = MenuBar.new
         | 
| 1051 | 
            +
                    @file_menu = FileMenu.new(@treeview)
         | 
| 1052 | 
            +
                    menu_bar.append @file_menu.create
         | 
| 1053 | 
            +
                    @edit_menu = EditMenu.new(@treeview)
         | 
| 1054 | 
            +
                    menu_bar.append @edit_menu.create
         | 
| 1055 | 
            +
                    @options_menu = OptionsMenu.new(@treeview)
         | 
| 1056 | 
            +
                    menu_bar.append @options_menu.create
         | 
| 1057 | 
            +
                    menu_bar
         | 
| 1058 | 
            +
                  end
         | 
| 1059 | 
            +
             | 
| 1060 | 
            +
                  # Sets editor status to changed, to indicate that the edited data
         | 
| 1061 | 
            +
                  # containts unsaved changes.
         | 
| 1062 | 
            +
                  def change
         | 
| 1063 | 
            +
                    @changed = true
         | 
| 1064 | 
            +
                    display_title
         | 
| 1065 | 
            +
                  end
         | 
| 1066 | 
            +
             | 
| 1067 | 
            +
                  # Sets editor status to unchanged, to indicate that the edited data
         | 
| 1068 | 
            +
                  # doesn't containt unsaved changes.
         | 
| 1069 | 
            +
                  def unchange
         | 
| 1070 | 
            +
                    @changed = false
         | 
| 1071 | 
            +
                    display_title
         | 
| 1072 | 
            +
                  end
         | 
| 1073 | 
            +
             | 
| 1074 | 
            +
                  # Puts a new model _model_ into the Gtk::TreeView to be edited.
         | 
| 1075 | 
            +
                  def view_new_model(model)
         | 
| 1076 | 
            +
                    @treeview.model     = model
         | 
| 1077 | 
            +
                    @treeview.expanded  = true
         | 
| 1078 | 
            +
                    @treeview.expand_all
         | 
| 1079 | 
            +
                    unchange
         | 
| 1080 | 
            +
                  end
         | 
| 1081 | 
            +
             | 
| 1082 | 
            +
                  # Displays _text_ in the status bar.
         | 
| 1083 | 
            +
                  def display_status(text)
         | 
| 1084 | 
            +
                    @cid ||= nil
         | 
| 1085 | 
            +
                    @status_bar.pop(@cid) if @cid
         | 
| 1086 | 
            +
                    @cid = @status_bar.get_context_id('dummy')
         | 
| 1087 | 
            +
                    @status_bar.push(@cid, text)
         | 
| 1088 | 
            +
                  end
         | 
| 1089 | 
            +
             | 
| 1090 | 
            +
                  # Opens a dialog, asking, if changes should be saved to a file.
         | 
| 1091 | 
            +
                  def ask_save
         | 
| 1092 | 
            +
                    if Editor.question_dialog(self,
         | 
| 1093 | 
            +
                      "Unsaved changes to JSON model. Save?")
         | 
| 1094 | 
            +
                      if @filename
         | 
| 1095 | 
            +
                        file_save
         | 
| 1096 | 
            +
                      else
         | 
| 1097 | 
            +
                        file_save_as
         | 
| 1098 | 
            +
                      end
         | 
| 1099 | 
            +
                    end
         | 
| 1100 | 
            +
                  end
         | 
| 1101 | 
            +
             | 
| 1102 | 
            +
                  # Quit this editor, that is, leave this editor's main loop.
         | 
| 1103 | 
            +
                  def quit
         | 
| 1104 | 
            +
                    ask_save if @changed
         | 
| 1105 | 
            +
                    destroy
         | 
| 1106 | 
            +
                    Gtk.main_quit
         | 
| 1107 | 
            +
                    true
         | 
| 1108 | 
            +
                  end
         | 
| 1109 | 
            +
             | 
| 1110 | 
            +
                  # Display the new title according to the editor's current state.
         | 
| 1111 | 
            +
                  def display_title
         | 
| 1112 | 
            +
                    title = TITLE.dup
         | 
| 1113 | 
            +
                    title << ": #@filename" if @filename
         | 
| 1114 | 
            +
                    title << " *" if @changed
         | 
| 1115 | 
            +
                    self.title = title
         | 
| 1116 | 
            +
                  end
         | 
| 1117 | 
            +
             | 
| 1118 | 
            +
                  # Clear the current model, after asking to save all unsaved changes.
         | 
| 1119 | 
            +
                  def clear
         | 
| 1120 | 
            +
                    ask_save if @changed
         | 
| 1121 | 
            +
                    @filename = nil
         | 
| 1122 | 
            +
                    self.view_new_model nil
         | 
| 1123 | 
            +
                  end
         | 
| 1124 | 
            +
             | 
| 1125 | 
            +
                  def check_pretty_printed(json)
         | 
| 1126 | 
            +
                    pretty = !!((nl_index = json.index("\n")) && nl_index != json.size - 1)
         | 
| 1127 | 
            +
                    @options_menu.pretty_item.active = pretty
         | 
| 1128 | 
            +
                  end
         | 
| 1129 | 
            +
                  private :check_pretty_printed
         | 
| 1130 | 
            +
             | 
| 1131 | 
            +
                  # Open the data at the location _uri_, if given. Otherwise open a dialog
         | 
| 1132 | 
            +
                  # to ask for the _uri_.
         | 
| 1133 | 
            +
                  def location_open(uri = nil)
         | 
| 1134 | 
            +
                    uri = ask_for_location unless uri
         | 
| 1135 | 
            +
                    uri or return
         | 
| 1136 | 
            +
                    data = load_location(uri) or return
         | 
| 1137 | 
            +
                    view_new_model Editor.data2model(data)
         | 
| 1138 | 
            +
                  end
         | 
| 1139 | 
            +
             | 
| 1140 | 
            +
                  # Open the file _filename_ or call the #select_file method to ask for a
         | 
| 1141 | 
            +
                  # filename.
         | 
| 1142 | 
            +
                  def file_open(filename = nil)
         | 
| 1143 | 
            +
                    filename = select_file('Open as a JSON file') unless filename
         | 
| 1144 | 
            +
                    data = load_file(filename) or return
         | 
| 1145 | 
            +
                    view_new_model Editor.data2model(data)
         | 
| 1146 | 
            +
                  end
         | 
| 1147 | 
            +
             | 
| 1148 | 
            +
                  # Save the current file.
         | 
| 1149 | 
            +
                  def file_save
         | 
| 1150 | 
            +
                    if @filename
         | 
| 1151 | 
            +
                      store_file(@filename)
         | 
| 1152 | 
            +
                    else
         | 
| 1153 | 
            +
                      file_save_as
         | 
| 1154 | 
            +
                    end
         | 
| 1155 | 
            +
                  end
         | 
| 1156 | 
            +
             | 
| 1157 | 
            +
                  # Save the current file as the filename 
         | 
| 1158 | 
            +
                  def file_save_as
         | 
| 1159 | 
            +
                    filename = select_file('Save as a JSON file')
         | 
| 1160 | 
            +
                    store_file(filename)
         | 
| 1161 | 
            +
                  end
         | 
| 1162 | 
            +
             | 
| 1163 | 
            +
                  # Store the current JSON document to _path_.
         | 
| 1164 | 
            +
                  def store_file(path)
         | 
| 1165 | 
            +
                    if path
         | 
| 1166 | 
            +
                      data = Editor.model2data(@treeview.model.iter_first)
         | 
| 1167 | 
            +
                      File.open(path + '.tmp', 'wb') do |output|
         | 
| 1168 | 
            +
                        if @options_menu.pretty_item.active?
         | 
| 1169 | 
            +
                          output.puts JSON.pretty_generate(data)
         | 
| 1170 | 
            +
                        else
         | 
| 1171 | 
            +
                          output.write JSON.unparse(data)
         | 
| 1172 | 
            +
                        end
         | 
| 1173 | 
            +
                      end
         | 
| 1174 | 
            +
                      File.rename path + '.tmp', path
         | 
| 1175 | 
            +
                      @filename = path
         | 
| 1176 | 
            +
                      toplevel.display_status("Saved data to '#@filename'.")
         | 
| 1177 | 
            +
                      unchange
         | 
| 1178 | 
            +
                    end
         | 
| 1179 | 
            +
                  rescue SystemCallError => e
         | 
| 1180 | 
            +
                    Editor.error_dialog(self, "Failed to store JSON file: #{e}!")
         | 
| 1181 | 
            +
                  end
         | 
| 1182 | 
            +
              
         | 
| 1183 | 
            +
                  # Load the file named _filename_ into the editor as a JSON document.
         | 
| 1184 | 
            +
                  def load_file(filename)
         | 
| 1185 | 
            +
                    if filename
         | 
| 1186 | 
            +
                      if File.directory?(filename)
         | 
| 1187 | 
            +
                        Editor.error_dialog(self, "Try to select a JSON file!")
         | 
| 1188 | 
            +
                        return
         | 
| 1189 | 
            +
                      else
         | 
| 1190 | 
            +
                        data = read_data(filename)
         | 
| 1191 | 
            +
                        @filename = filename
         | 
| 1192 | 
            +
                        toplevel.display_status("Loaded data from '#@filename'.")
         | 
| 1193 | 
            +
                        display_title
         | 
| 1194 | 
            +
                        return data
         | 
| 1195 | 
            +
                      end
         | 
| 1196 | 
            +
                    end
         | 
| 1197 | 
            +
                  end
         | 
| 1198 | 
            +
             | 
| 1199 | 
            +
                  # Load the data at location _uri_ into the editor as a JSON document.
         | 
| 1200 | 
            +
                  def load_location(uri)
         | 
| 1201 | 
            +
                    data = read_data(uri)
         | 
| 1202 | 
            +
                    @filename = nil
         | 
| 1203 | 
            +
                    toplevel.display_status("Loaded data from '#{uri}'.")
         | 
| 1204 | 
            +
                    display_title
         | 
| 1205 | 
            +
                    data
         | 
| 1206 | 
            +
                  end
         | 
| 1207 | 
            +
             | 
| 1208 | 
            +
                  # Read a JSON document from the file named _filename_, parse it into a
         | 
| 1209 | 
            +
                  # ruby data structure, and return the data.
         | 
| 1210 | 
            +
                  def read_data(filename)
         | 
| 1211 | 
            +
                    open(filename) do |f|
         | 
| 1212 | 
            +
                      json = f.read
         | 
| 1213 | 
            +
                      check_pretty_printed(json)
         | 
| 1214 | 
            +
                      if @encoding && !/^utf8$/i.match(@encoding)
         | 
| 1215 | 
            +
                        iconverter = Iconv.new('utf8', @encoding)
         | 
| 1216 | 
            +
                        json = iconverter.iconv(json)
         | 
| 1217 | 
            +
                      end
         | 
| 1218 | 
            +
                      return JSON::parse(json, :max_nesting => false)
         | 
| 1219 | 
            +
                    end
         | 
| 1220 | 
            +
                  rescue JSON::JSONError => e
         | 
| 1221 | 
            +
                    Editor.error_dialog(self, "Failed to parse JSON file: #{e}!")
         | 
| 1222 | 
            +
                    return
         | 
| 1223 | 
            +
                  rescue SystemCallError => e
         | 
| 1224 | 
            +
                    quit
         | 
| 1225 | 
            +
                  end
         | 
| 1226 | 
            +
             | 
| 1227 | 
            +
                  # Open a file selecton dialog, displaying _message_, and return the
         | 
| 1228 | 
            +
                  # selected filename or nil, if no file was selected.
         | 
| 1229 | 
            +
                  def select_file(message)
         | 
| 1230 | 
            +
                    filename = nil
         | 
| 1231 | 
            +
                    fs = FileSelection.new(message).set_modal(true).
         | 
| 1232 | 
            +
                      set_filename(Dir.pwd + "/").set_transient_for(self)
         | 
| 1233 | 
            +
                    fs.signal_connect(:destroy) { Gtk.main_quit }
         | 
| 1234 | 
            +
                    fs.ok_button.signal_connect(:clicked) do
         | 
| 1235 | 
            +
                      filename = fs.filename
         | 
| 1236 | 
            +
                      fs.destroy
         | 
| 1237 | 
            +
                      Gtk.main_quit
         | 
| 1238 | 
            +
                    end
         | 
| 1239 | 
            +
                    fs.cancel_button.signal_connect(:clicked) do
         | 
| 1240 | 
            +
                      fs.destroy
         | 
| 1241 | 
            +
                      Gtk.main_quit
         | 
| 1242 | 
            +
                    end
         | 
| 1243 | 
            +
                    fs.show_all
         | 
| 1244 | 
            +
                    Gtk.main
         | 
| 1245 | 
            +
                    filename
         | 
| 1246 | 
            +
                  end
         | 
| 1247 | 
            +
             | 
| 1248 | 
            +
                  # Ask for location URI a to load data from. Returns the URI as a string.
         | 
| 1249 | 
            +
                  def ask_for_location
         | 
| 1250 | 
            +
                    dialog = Dialog.new(
         | 
| 1251 | 
            +
                      "Load data from location...",
         | 
| 1252 | 
            +
                      nil, nil,
         | 
| 1253 | 
            +
                      [ Stock::OK, Dialog::RESPONSE_ACCEPT ],
         | 
| 1254 | 
            +
                      [ Stock::CANCEL, Dialog::RESPONSE_REJECT ]
         | 
| 1255 | 
            +
                    )
         | 
| 1256 | 
            +
                    hbox = HBox.new(false, 5)
         | 
| 1257 | 
            +
             | 
| 1258 | 
            +
                    hbox.add(Label.new("Location:"))
         | 
| 1259 | 
            +
                    hbox.pack_start(location_input = Entry.new)
         | 
| 1260 | 
            +
                    location_input.width_chars = 60
         | 
| 1261 | 
            +
                    location_input.text = @location || ''
         | 
| 1262 | 
            +
             | 
| 1263 | 
            +
                    dialog.vbox.add(hbox)
         | 
| 1264 | 
            +
             | 
| 1265 | 
            +
                    dialog.signal_connect(:'key-press-event', &DEFAULT_DIALOG_KEY_PRESS_HANDLER)
         | 
| 1266 | 
            +
                    dialog.show_all
         | 
| 1267 | 
            +
                    dialog.run do |response| 
         | 
| 1268 | 
            +
                      if response == Dialog::RESPONSE_ACCEPT
         | 
| 1269 | 
            +
                        return @location = location_input.text
         | 
| 1270 | 
            +
                      end
         | 
| 1271 | 
            +
                    end
         | 
| 1272 | 
            +
                    return
         | 
| 1273 | 
            +
                  ensure
         | 
| 1274 | 
            +
                    dialog.destroy if dialog
         | 
| 1275 | 
            +
                  end
         | 
| 1276 | 
            +
                end
         | 
| 1277 | 
            +
             | 
| 1278 | 
            +
                class << self
         | 
| 1279 | 
            +
                  # Starts a JSON Editor. If a block was given, it yields
         | 
| 1280 | 
            +
                  # to the JSON::Editor::MainWindow instance.
         | 
| 1281 | 
            +
                  def start(encoding = nil) # :yield: window
         | 
| 1282 | 
            +
                    encoding ||= 'utf8'
         | 
| 1283 | 
            +
                    Gtk.init
         | 
| 1284 | 
            +
                    @window = Editor::MainWindow.new(encoding)
         | 
| 1285 | 
            +
                    @window.icon_list = [ Editor.fetch_icon('json') ]
         | 
| 1286 | 
            +
                    yield @window if block_given?
         | 
| 1287 | 
            +
                    @window.show_all
         | 
| 1288 | 
            +
                    Gtk.main
         | 
| 1289 | 
            +
                  end
         | 
| 1290 | 
            +
             | 
| 1291 | 
            +
                  attr_reader :window
         | 
| 1292 | 
            +
                end
         | 
| 1293 | 
            +
              end
         | 
| 1294 | 
            +
            end
         | 
| 1295 | 
            +
              # vim: set et sw=2 ts=2:
         |