1 module unde.viewers.text_viewer.lib; 2 3 import unde.global_state; 4 import unde.path_mnt; 5 import unde.lib; 6 import unde.slash; 7 import unde.command_line.lib; 8 9 import berkeleydb.all; 10 11 import derelict.sdl2.sdl; 12 import derelict.sdl2.ttf; 13 14 import core.exception; 15 16 import std..string; 17 import std.stdio; 18 import std.algorithm.sorting; 19 import std.math; 20 import std.utf; 21 22 import std.file; 23 24 /* 25 void 26 text_viewer(GlobalState gs, PathMnt path) 27 { 28 } 29 30 void 31 draw_text(GlobalState gs) 32 { 33 } 34 35 package void 36 text_next(GlobalState gs) 37 { 38 } 39 40 package void 41 text_prev(GlobalState gs) 42 { 43 } 44 */ 45 46 void 47 text_viewer(GlobalState gs, PathMnt path) 48 { 49 auto old_state = gs.state; 50 gs.state = State.TextViewer; 51 gs.text_viewer.path = path; 52 53 get_rectsize(gs); 54 55 if (old_state != State.TextViewer) 56 { 57 calculate_positions_in_directories(gs); 58 } 59 60 gs.dirty = true; 61 } 62 63 private void 64 get_rectsize(GlobalState gs) 65 { 66 with (gs.text_viewer) 67 { 68 Dbt key, data; 69 string path0 = path.get_key(gs.lsblk); 70 key = path0; 71 //writefln("GET %s", path0.replace("\0", SL)); 72 auto res = gs.db_map.get(null, &key, &data); 73 74 if (res == 0) 75 { 76 rectsize = data.to!(RectSize); 77 } 78 } 79 } 80 81 package void 82 put_rectsize(GlobalState gs) 83 { 84 with (gs.text_viewer) 85 { 86 Dbt key, data; 87 string path0 = path.get_key(gs.lsblk); 88 key = path0; 89 data = rectsize; 90 auto res = gs.db_map.put(null, &key, &data); 91 if (res != 0) 92 throw new Exception("Path info to map-db not written"); 93 } 94 } 95 96 package int 97 measure_lines(GlobalState gs, string text, 98 int size, bool wrap_lines, int line_height, SDL_Color color) 99 { 100 if (text.length > 0 && text[$-1] == '\r') text = text[0..$-1]; 101 int lines = 1; 102 103 if (!wrap_lines) return 1; 104 105 auto tt = gs.text_viewer.font.get_line_from_cache(text, 106 size, gs.screen.w, line_height, color); 107 108 lines = tt.h/line_height; 109 110 return lines; 111 } 112 113 package void 114 selection_to_buffer(GlobalState gs) 115 { 116 File file; 117 118 with(gs.text_viewer) 119 { 120 try { 121 file = File(path); 122 } 123 catch (Exception exp) 124 { 125 return; 126 } 127 128 file.seek(start_selection); 129 string selection = file.rawRead(new char[end_selection-start_selection+1]).idup(); 130 131 SDL_SetClipboardText(selection.toStringz()); 132 } 133 } 134 135 void 136 draw_text(GlobalState gs) 137 { 138 File file; 139 140 with(gs.text_viewer) 141 { 142 if (SDL_GetTicks() - last_redraw > 200) 143 { 144 int r = SDL_SetRenderTarget(gs.renderer, texture); 145 if (r < 0) 146 { 147 throw new Exception(format("Error while set render target text_viewer.texture: %s", 148 fromStringz(SDL_GetError()) )); 149 } 150 151 SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_BLEND); 152 r = SDL_RenderClear(gs.renderer); 153 if (r < 0) 154 { 155 throw new Exception(format("Error while clear renderer: %s", 156 fromStringz(SDL_GetError()) )); 157 } 158 159 try { 160 file = File(path); 161 } 162 catch (Exception exp) 163 { 164 return; 165 } 166 167 try{ 168 int line_height = cast(int)(round(SQRT2^^gs.text_viewer.fontsize)*1.2); 169 auto color = SDL_Color(255, 255, 255, 255); 170 171 bool rectsize_changed = false; 172 173 long blocksize = 4096; 174 while (y > 0 && rectsize.offset > 0) 175 { 176 long offset = rectsize.offset - blocksize; 177 if (offset < 0) offset = 0; 178 file.seek(offset); 179 180 long lines = 0; 181 182 struct lineInfo 183 { 184 int lines; 185 long offset; 186 } 187 188 lineInfo[] lines_info; 189 190 long startline_offset = file.tell; 191 foreach(line; file.byLine()) 192 { 193 if (lines == 0 && offset != 0) 194 { 195 lines++; 196 startline_offset = file.tell; 197 continue; 198 } 199 if (startline_offset >= rectsize.offset) break; 200 201 int wrapped_lines = measure_lines(gs, line.idup(), 202 fontsize, wraplines, line_height, color); 203 lines_info ~= lineInfo(wrapped_lines, startline_offset); 204 205 lines++; 206 startline_offset = file.tell; 207 } 208 209 if (lines_info.length == 0) 210 { 211 blocksize *= 2; 212 continue; 213 } 214 215 foreach_reverse(line_info; lines_info) 216 { 217 y -= line_info.lines * line_height; 218 rectsize.offset = line_info.offset; 219 rectsize_changed = true; 220 if (y <= 0) break; 221 } 222 } 223 224 file.seek(rectsize.offset); 225 long lines = 0; 226 227 int line_y = y; 228 int line_x = x; 229 auto offset = rectsize.offset; 230 foreach(line; file.byLine()) 231 { 232 if (line_y > gs.screen.h) 233 { 234 break; 235 } 236 237 ssize_t from, to; 238 if (offset + line.length < start_selection || 239 offset > end_selection) 240 { 241 from = -1; 242 to = -1; 243 } 244 else 245 { 246 if (start_selection > offset) 247 { 248 from = cast(ssize_t)(start_selection - offset); 249 } 250 if (offset > start_selection) 251 { 252 from = 0; 253 } 254 if (offset < end_selection) 255 { 256 to = cast(ssize_t)(end_selection - offset); 257 } 258 if ( offset + line.length < end_selection ) 259 { 260 to = line.length; 261 } 262 } 263 264 auto tt = gs.text_viewer.font.get_line_from_cache(line.idup(), 265 fontsize, wraplines?gs.screen.w:0, line_height, color, null, from, to); 266 267 auto dst = SDL_Rect(line_x, line_y, tt.w, tt.h); 268 269 r = SDL_RenderCopyEx(gs.renderer, tt.texture, null, &dst, 0, 270 null, SDL_FLIP_NONE); 271 if (r < 0) 272 { 273 writefln( "draw_text(): Error while render copy: %s", fromStringz(SDL_GetError()) ); 274 } 275 276 if (gs.mouse_screen_y >= dst.y && gs.mouse_screen_y <= dst.y+dst.h) 277 { 278 mouse_offset = cast(ssize_t)(offset + get_position_by_chars( 279 gs.mouse_screen_x - dst.x, 280 gs.mouse_screen_y - dst.y, tt.chars)); 281 } 282 283 if (line_y + tt.h < 0) 284 { 285 y += tt.h; 286 rectsize.offset = file.tell; 287 rectsize_changed = true; 288 } 289 290 lines += tt.h/line_height; 291 line_y += tt.h; 292 offset = file.tell; 293 } 294 295 if (rectsize_changed) 296 put_rectsize(gs); 297 298 //return; 299 } 300 catch(UTFException exc) 301 { 302 //return; 303 } 304 305 last_redraw = SDL_GetTicks(); 306 307 r = SDL_SetRenderTarget(gs.renderer, null); 308 if (r < 0) 309 { 310 throw new Exception(format("Error while restore render target: %s", 311 fromStringz(SDL_GetError()) )); 312 } 313 314 font.clear_chars_cache(); 315 font.clear_lines_cache(); 316 } 317 318 if (path in gs.selection_hash) 319 { 320 int r = SDL_RenderCopy(gs.renderer, gs.texture_blue, null, null); 321 if (r < 0) 322 { 323 writefln( "draw_text(): Error while render copy texture_blue: %s", fromStringz(SDL_GetError()) ); 324 } 325 } 326 327 SDL_Rect dst = SDL_Rect(0, 0, gs.screen.w, gs.screen.h); 328 int r = SDL_RenderCopy(gs.renderer, texture, null, &dst); 329 if (r < 0) 330 { 331 writefln( "draw_text(): Error while render copy texture: %s", fromStringz(SDL_GetError()) ); 332 } 333 } 334 } 335 336 private void 337 positions_in_directories_recursive(GlobalState gs, string p, ssize_t lev = 0) 338 { 339 with (gs.text_viewer) 340 { 341 if (lev == 0) 342 { 343 files = []; 344 positions = []; 345 } 346 347 string[] paths; 348 349 try 350 { 351 foreach (string name; dirEntries(p, SpanMode.shallow)) 352 { 353 paths ~= name; 354 } 355 } 356 catch (FileException e) 357 { 358 return; 359 } 360 361 sort!("a < b")(paths); 362 363 bool found = false; 364 foreach (i, name; paths) 365 { 366 if (path.startsWith(name) && (name.length+1 > path.length || path[name.length] == SL[0])) 367 { 368 found = true; 369 positions ~= i; 370 files ~= paths; 371 372 if (name == path) 373 { 374 level = lev; 375 } 376 else 377 { 378 positions_in_directories_recursive(gs, name, level+1); 379 } 380 break; 381 } 382 } 383 if (!found) 384 { 385 gs.state = State.FileManager; 386 gs.dirty = true; 387 } 388 } 389 } 390 391 private void 392 calculate_positions_in_directories(GlobalState gs) 393 { 394 with (gs.text_viewer) 395 { 396 selections = []; 397 398 foreach (selection; gs.selection_hash.byKey()) 399 { 400 if ( mime(selection) != "inode/directory" ) continue; 401 402 bool found_subdirs = false; 403 //writefln("%s", selection); 404 rescan_selections: 405 foreach (i, ref s; selections) 406 { 407 if ( selection.startsWith(s) ) 408 { 409 //writefln("1. %s starts with %s", selection, s); 410 // Do nothing, the super-directory in the selections 411 found_subdirs = true; 412 } 413 else if ( s.startsWith(selection) ) 414 { 415 //writefln("2. %s starts with %s", s, selection); 416 // selections consists subdirectory 417 if (found_subdirs) 418 { 419 //writefln("before selections=%s", selections); 420 if (i < selection.length-1) 421 selections = selections[0..i] ~ selections[i+1..$]; 422 else 423 selections = selections[0..i]; 424 //writefln("after selections=%s", selections); 425 goto rescan_selections; 426 } 427 else 428 { 429 s = selection; 430 found_subdirs = true; 431 } 432 } 433 } 434 435 if (!found_subdirs) 436 { 437 selections ~= selection; 438 } 439 } 440 441 if (selections.length == 0) 442 { 443 string dir = getParent(path); 444 selections ~= dir; 445 } 446 447 //writefln("selections = %s", selections); 448 449 sel = -1; 450 foreach (i, s; selections) 451 { 452 if (path.startsWith(s)) 453 { 454 sel = i; 455 } 456 } 457 458 if (sel >= 0) 459 { 460 positions_in_directories_recursive(gs, selections[sel]); 461 } 462 463 /*writefln("positions = %s", positions); 464 writefln("files = %s", files); 465 writefln("level = %s", level);*/ 466 } 467 } 468 469 package void 470 text_next(GlobalState gs) 471 { 472 with (gs.text_viewer) 473 { 474 int sel0_count = 0; 475 bool found_text = false; 476 do 477 { 478 if (sel >= 0 && level >= 0) 479 { 480 bool level_dec; 481 do 482 { 483 level_dec = false; 484 positions[level]++; 485 //writefln("positions[%s] = %s", level, positions[level]); 486 //writefln("files = %s", files); 487 if ( positions[level] >= files[level].length ) 488 { 489 //writefln("files[%s] exceeded", level); 490 files = files[0..$-1]; 491 positions = positions[0..$-1]; 492 level--; 493 level_dec = true; 494 if (level == -1) 495 { 496 //writefln("level == -1"); 497 sel++; 498 if (sel >= selections.length) 499 { 500 //writefln("sel = 0"); 501 sel = 0; 502 sel0_count++; 503 } 504 } 505 } 506 } while (level_dec && level >= 0); 507 } 508 else if (sel == -1) 509 { 510 sel = 0; 511 sel0_count++; 512 } 513 514 //writefln("level = %s", level); 515 if (level == -1) 516 { 517 string[] paths; 518 try 519 { 520 foreach (string name; dirEntries(selections[sel], SpanMode.shallow)) 521 { 522 paths ~= name; 523 } 524 } 525 catch (FileException e) 526 { 527 return; 528 } 529 530 sort!("a < b")(paths); 531 532 positions ~= 0; 533 files ~= paths; 534 level++; 535 //writefln("0 paths added"); 536 } 537 538 do 539 { 540 if (files[level].length == 0) break; 541 string p = files[level][positions[level]]; 542 //writefln("p=%s", p); 543 string mime_type = mime(p); 544 if (mime_type == "inode/directory") 545 { 546 //writefln("directory"); 547 string[] paths; 548 549 try 550 { 551 foreach (string name; dirEntries(p, SpanMode.shallow)) 552 { 553 paths ~= name; 554 } 555 } 556 catch (FileException e) 557 { 558 return; 559 } 560 561 if (paths.length == 0) 562 { 563 break; 564 } 565 566 sort!("a < b")(paths); 567 568 positions ~= 0; 569 files ~= paths; 570 level++; 571 } 572 else if (mime_type.startsWith("text/")) 573 { 574 //writefln("image!"); 575 auto pathmnt = PathMnt(gs.lsblk, p); 576 text_viewer(gs, pathmnt); 577 found_text = true; 578 break; 579 } 580 else 581 { 582 break; 583 } 584 } while (true); 585 } while(sel0_count < 2 && !found_text); 586 } 587 } 588 589 package void 590 text_prev(GlobalState gs) 591 { 592 with (gs.text_viewer) 593 { 594 int seln_count = 0; 595 bool found_text = false; 596 do 597 { 598 if (sel >= 0 && level >= 0) 599 { 600 bool level_dec; 601 do 602 { 603 level_dec = false; 604 positions[level]--; 605 //writefln("positions[%s] = %s", level, positions[level]); 606 //writefln("files = %s", files); 607 if ( positions[level] < 0 ) 608 { 609 //writefln("files[%s] exceeded", level); 610 files = files[0..$-1]; 611 positions = positions[0..$-1]; 612 level--; 613 level_dec = true; 614 if (level == -1) 615 { 616 //writefln("level == -1"); 617 sel--; 618 if (sel < 0) 619 { 620 //writefln("sel = 0"); 621 sel = selections.length-1; 622 seln_count++; 623 } 624 } 625 } 626 } while (level_dec && level >= 0); 627 } 628 else if (sel == -1) 629 { 630 sel = selections.length-1; 631 seln_count++; 632 } 633 634 //writefln("level = %s", level); 635 if (level == -1) 636 { 637 string[] paths; 638 try 639 { 640 foreach (string name; dirEntries(selections[sel], SpanMode.shallow)) 641 { 642 paths ~= name; 643 } 644 } 645 catch (FileException e) 646 { 647 return; 648 } 649 650 sort!("a < b")(paths); 651 652 positions ~= paths.length-1; 653 files ~= paths; 654 level++; 655 //writefln("0 paths added"); 656 } 657 658 do 659 { 660 if (files[level].length == 0) break; 661 string p = files[level][positions[level]]; 662 //writefln("p=%s", p); 663 string mime_type = mime(p); 664 if (mime_type == "inode/directory") 665 { 666 //writefln("directory"); 667 string[] paths; 668 669 try 670 { 671 foreach (string name; dirEntries(p, SpanMode.shallow)) 672 { 673 paths ~= name; 674 } 675 } 676 catch (FileException e) 677 { 678 return; 679 } 680 681 if (paths.length == 0) 682 { 683 break; 684 } 685 686 sort!("a < b")(paths); 687 688 positions ~= paths.length-1; 689 files ~= paths; 690 level++; 691 } 692 else if (mime_type.startsWith("text/")) 693 { 694 //writefln("image!"); 695 auto pathmnt = PathMnt(gs.lsblk, p); 696 text_viewer(gs, pathmnt); 697 found_text = true; 698 break; 699 } 700 else 701 { 702 break; 703 } 704 } while (true); 705 } while(seln_count < 2 && !found_text); 706 } 707 }