1 module unde.viewers.image_viewer.lib; 2 3 import unde.global_state; 4 import unde.path_mnt; 5 import unde.lib; 6 import unde.slash; 7 8 import berkeleydb.all; 9 10 import derelict.sdl2.sdl; 11 import derelict.sdl2.image; 12 13 import std..string; 14 import std.stdio; 15 import std.algorithm.sorting; 16 import std.math; 17 import core.memory; 18 19 import std.file; 20 21 void 22 image_viewer(GlobalState gs, PathMnt p) 23 { 24 with (gs.image_viewer) 25 { 26 auto old_state = gs.state; 27 gs.state = State.ImageViewer; 28 path = p; 29 30 rect = SDL_Rect(0, 0, gs.screen.w, gs.screen.h); 31 32 auto st = get_image_from_cache(gs, path); 33 texture_tick = st; 34 get_rectsize(gs); 35 if (st != null) 36 { 37 if (st.w > gs.screen.w || st.h > gs.screen.h) 38 { 39 setup_0_scale(gs); 40 } 41 else 42 { 43 setup_1_scale(gs); 44 } 45 } 46 47 if (old_state != State.ImageViewer) 48 { 49 calculate_positions_in_directories(gs); 50 } 51 52 gs.dirty = true; 53 } 54 } 55 56 package void 57 setup_0_scale(GlobalState gs) 58 { 59 with (gs.image_viewer) 60 { 61 with (gs.image_viewer.texture_tick) 62 { 63 if (texture) 64 { 65 int w = w; 66 int h = h; 67 68 if (rectsize.angle == 90 || rectsize.angle == 270) 69 { 70 if (cast(double)(w)/h > cast(double)(gs.screen.w)/gs.screen.h) 71 { 72 /* EN: Center and scale down picture to take all width 73 of screen 74 RU: Отцентрировать и уменьшить картинку так, чтобы 75 она заняла всю ширину экрана */ 76 rect.x = (gs.screen.w - gs.screen.h)/2; 77 rect.y = (gs.screen.w*h/w - gs.screen.h*h/w)/2 + (gs.screen.h-gs.screen.w*h/w)/2; 78 rect.w = gs.screen.h; 79 rect.h = gs.screen.h*h/w; 80 } 81 else 82 { 83 /* EN: Center and scale down picture to take all height 84 of screen 85 RU: Отцентрировать и уменьшить картинку так, чтобы 86 она заняла всю высоту экрана */ 87 rect.x = (gs.screen.h*w/h - gs.screen.h)/2 + (gs.screen.w-gs.screen.h*w/h)/2; 88 rect.y = (gs.screen.h - gs.screen.h*h/w)/2; 89 rect.w = gs.screen.h; 90 rect.h = gs.screen.h*h/w; 91 } 92 } 93 else 94 { 95 if (cast(double)(w)/h > cast(double)(gs.screen.w)/gs.screen.h) 96 { 97 /* EN: Center and scale down picture to take all width 98 of screen 99 RU: Отцентрировать и уменьшить картинку так, чтобы 100 она заняла всю ширину экрана */ 101 rect.x = 0; 102 rect.y = (gs.screen.h-gs.screen.w*h/w)/2; 103 rect.w = gs.screen.w; 104 rect.h = gs.screen.w*h/w; 105 } 106 else 107 { 108 /* EN: Center and scale down picture to take all height 109 of screen 110 RU: Отцентрировать и уменьшить картинку так, чтобы 111 она заняла всю высоту экрана */ 112 rect.x = (gs.screen.w-gs.screen.h*w/h)/2; 113 rect.y = 0; 114 rect.w = gs.screen.h*w/h; 115 rect.h = gs.screen.h; 116 } 117 } 118 } 119 } 120 } 121 } 122 123 package void 124 setup_1_scale(GlobalState gs) 125 { 126 with (gs.image_viewer) 127 { 128 with (gs.image_viewer.texture_tick) 129 { 130 if (texture) 131 { 132 rect = SDL_Rect(0, 0, w, h); 133 if (rectsize.angle == 90 || rectsize.angle == 270) 134 { 135 rect.x = -(w-h)/2; 136 rect.y = (w-h)/2; 137 } 138 } 139 } 140 } 141 } 142 143 private void 144 get_rectsize(GlobalState gs) 145 { 146 with (gs.image_viewer) 147 { 148 Dbt key, data; 149 string path0 = path.get_key(gs.lsblk); 150 key = path0; 151 //writefln("GET %s", path0.replace("\0", SL)); 152 auto res = gs.db_map.get(null, &key, &data); 153 154 if (res == 0) 155 { 156 rectsize = data.to!(RectSize); 157 } 158 } 159 } 160 161 package void 162 put_rectsize(GlobalState gs) 163 { 164 with (gs.image_viewer) 165 { 166 Dbt key, data; 167 string path0 = path.get_key(gs.lsblk); 168 key = path0; 169 data = rectsize; 170 auto res = gs.db_map.put(null, &key, &data); 171 if (res != 0) 172 throw new Exception("Path info to map-db not written"); 173 } 174 } 175 176 auto 177 get_image_from_cache(GlobalState gs, 178 in string p) 179 { 180 /* EN: Get picture from cache or load it from file 181 RU: Получить картинку из кеша или загрузить из файла */ 182 with(gs.image_viewer) 183 { 184 auto st = p in image_cache; 185 if (st) 186 { 187 st.tick = SDL_GetTicks(); 188 last_image_cache_use = SDL_GetTicks(); 189 } 190 else 191 { 192 auto image = IMG_Load(p.toStringz); 193 194 if (image) 195 { 196 auto image_texture = 197 SDL_CreateTextureFromSurface(gs.renderer, image); 198 image_cache[p] = Texture_Tick(image.w, image.h, [], image_texture, SDL_GetTicks()); 199 SDL_FreeSurface(image); 200 last_image_cache_use = SDL_GetTicks(); 201 st = path in image_cache; 202 } 203 } 204 205 return st; 206 } 207 } 208 209 /* EN: clear cache from old entries 210 RU: очистить кеш от старых элементов */ 211 void 212 clear_image_cache(GlobalState gs) 213 { 214 int cleared; 215 with(gs.image_viewer) 216 { 217 foreach(k, v; image_cache) 218 { 219 if (v.tick < last_image_cache_use - 30_000) 220 { 221 cleared++; 222 if (v.texture) SDL_DestroyTexture(v.texture); 223 if (!image_cache.remove(k)) 224 { 225 writefln("Can't remove image cache key %s", k); 226 } 227 writefln("v.tick = %s < %s. Remove key %s", 228 v.tick, last_image_cache_use - 30_000, k); 229 } 230 } 231 } 232 if (cleared) 233 { 234 //writefln("Cleared %d objects from lines cache", cleared); 235 GC.collect(); 236 } 237 } 238 239 void 240 draw_image(GlobalState gs) 241 { 242 with(gs.image_viewer) 243 { 244 texture_tick = get_image_from_cache(gs, path); 245 clear_image_cache(gs); 246 if (texture_tick && texture_tick.texture) 247 { 248 if (path in gs.selection_hash) 249 { 250 SDL_Rect dst = SDL_Rect(0, 0, gs.screen.w, gs.screen.h); 251 int r = SDL_RenderCopy(gs.renderer, gs.texture_blue, null, &dst); 252 if (r < 0) 253 { 254 writefln( "draw_image(): Error while render copy: %s", fromStringz(SDL_GetError()) ); 255 } 256 } 257 258 int r = SDL_RenderCopyEx(gs.renderer, texture_tick.texture, null, &gs.image_viewer.rect, rectsize.angle, 259 null, SDL_FLIP_NONE); 260 if (r < 0) 261 { 262 writefln( "draw_image(): Error while render copy: %s", fromStringz(SDL_GetError()) ); 263 } 264 265 if (path in gs.selection_hash && 266 abs(gs.image_viewer.rect.w - gs.screen.w) < 20 && (gs.image_viewer.rect.h - gs.screen.h) < 20) 267 { 268 auto rect = SDL_Rect(0, 0, gs.screen.w, 32); 269 r = SDL_RenderCopy(gs.renderer, gs.texture_blue, null, &rect); 270 if (r < 0) 271 { 272 writefln( "draw_image(): Error while render copy: %s", fromStringz(SDL_GetError()) ); 273 } 274 } 275 } 276 } 277 } 278 279 private void 280 positions_in_directories_recursive(GlobalState gs, string p, ssize_t lev = 0) 281 { 282 with (gs.image_viewer) 283 { 284 if (lev == 0) 285 { 286 files = []; 287 positions = []; 288 } 289 290 string[] paths; 291 292 try 293 { 294 foreach (string name; dirEntries(p, SpanMode.shallow)) 295 { 296 paths ~= name; 297 } 298 } 299 catch (FileException e) 300 { 301 return; 302 } 303 304 sort!("a < b")(paths); 305 306 bool found = false; 307 foreach (i, name; paths) 308 { 309 if (path.startsWith(name) && (name.length+1 > path.length || path[name.length] == SL[0])) 310 { 311 found = true; 312 positions ~= i; 313 files ~= paths; 314 315 if (name == path) 316 { 317 level = lev; 318 } 319 else 320 { 321 positions_in_directories_recursive(gs, name, level+1); 322 } 323 break; 324 } 325 } 326 assert(found); 327 } 328 } 329 330 private void 331 calculate_positions_in_directories(GlobalState gs) 332 { 333 with (gs.image_viewer) 334 { 335 selections = []; 336 337 foreach (selection; gs.selection_hash.byKey()) 338 { 339 if ( mime(selection) != "inode/directory" ) continue; 340 341 bool found_subdirs = false; 342 //writefln("%s", selection); 343 rescan_selections: 344 foreach (i, ref s; selections) 345 { 346 if ( selection.startsWith(s) ) 347 { 348 //writefln("1. %s starts with %s", selection, s); 349 // Do nothing, the super-directory in the selections 350 found_subdirs = true; 351 } 352 else if ( s.startsWith(selection) ) 353 { 354 //writefln("2. %s starts with %s", s, selection); 355 // selections consists subdirectory 356 if (found_subdirs) 357 { 358 //writefln("before selections=%s", selections); 359 if (i < selection.length-1) 360 selections = selections[0..i] ~ selections[i+1..$]; 361 else 362 selections = selections[0..i]; 363 //writefln("after selections=%s", selections); 364 goto rescan_selections; 365 } 366 else 367 { 368 s = selection; 369 found_subdirs = true; 370 } 371 } 372 } 373 374 if (!found_subdirs) 375 { 376 selections ~= selection; 377 } 378 } 379 380 if (selections.length == 0) 381 { 382 string dir = getParent(path); 383 selections ~= dir; 384 } 385 386 /*writefln("selections = %s", selections);*/ 387 388 sel = -1; 389 foreach (i, s; selections) 390 { 391 if (path.startsWith(s)) 392 { 393 sel = i; 394 } 395 } 396 397 if (sel >= 0) 398 { 399 positions_in_directories_recursive(gs, selections[sel]); 400 } 401 402 /*writefln("positions = %s", positions); 403 writefln("files = %s", files); 404 writefln("level = %s", level);*/ 405 } 406 } 407 408 package void 409 image_next(GlobalState gs) 410 { 411 with (gs.image_viewer) 412 { 413 int sel0_count = 0; 414 bool found_image = false; 415 do 416 { 417 if (sel >= 0 && level >= 0) 418 { 419 bool level_dec; 420 do 421 { 422 level_dec = false; 423 positions[level]++; 424 //writefln("positions[%s] = %s", level, positions[level]); 425 //writefln("files = %s", files); 426 if ( positions[level] >= files[level].length ) 427 { 428 //writefln("files[%s] exceeded", level); 429 files = files[0..$-1]; 430 positions = positions[0..$-1]; 431 level--; 432 level_dec = true; 433 if (level == -1) 434 { 435 //writefln("level == -1"); 436 sel++; 437 if (sel >= selections.length) 438 { 439 //writefln("sel = 0"); 440 sel = 0; 441 sel0_count++; 442 } 443 } 444 } 445 } while (level_dec && level >= 0); 446 } 447 else if (sel == -1) 448 { 449 sel = 0; 450 sel0_count++; 451 } 452 453 //writefln("level = %s", level); 454 if (level == -1) 455 { 456 string[] paths; 457 try 458 { 459 foreach (string name; dirEntries(selections[sel], SpanMode.shallow)) 460 { 461 paths ~= name; 462 } 463 } 464 catch (FileException e) 465 { 466 return; 467 } 468 469 sort!("a < b")(paths); 470 471 positions ~= 0; 472 files ~= paths; 473 level++; 474 //writefln("0 paths added"); 475 } 476 477 do 478 { 479 if (files[level].length == 0) break; 480 string p = files[level][positions[level]]; 481 //writefln("p=%s", p); 482 string mime_type = mime(p); 483 if (mime_type == "inode/directory") 484 { 485 //writefln("directory"); 486 string[] paths; 487 488 try 489 { 490 foreach (string name; dirEntries(p, SpanMode.shallow)) 491 { 492 paths ~= name; 493 } 494 } 495 catch (FileException e) 496 { 497 return; 498 } 499 500 if (paths.length == 0) 501 { 502 break; 503 } 504 505 sort!("a < b")(paths); 506 507 positions ~= 0; 508 files ~= paths; 509 level++; 510 } 511 else if (mime_type.startsWith("image/")) 512 { 513 //writefln("image!"); 514 auto pathmnt = PathMnt(gs.lsblk, p); 515 image_viewer(gs, pathmnt); 516 found_image = true; 517 break; 518 } 519 else 520 { 521 break; 522 } 523 } while (true); 524 } while(sel0_count < 2 && !found_image); 525 } 526 } 527 528 package void 529 image_prev(GlobalState gs) 530 { 531 with (gs.image_viewer) 532 { 533 int seln_count = 0; 534 bool found_image = false; 535 do 536 { 537 if (sel >= 0 && level >= 0) 538 { 539 bool level_dec; 540 do 541 { 542 level_dec = false; 543 positions[level]--; 544 //writefln("positions[%s] = %s", level, positions[level]); 545 //writefln("files = %s", files); 546 if ( positions[level] < 0 ) 547 { 548 //writefln("files[%s] exceeded", level); 549 files = files[0..$-1]; 550 positions = positions[0..$-1]; 551 level--; 552 level_dec = true; 553 if (level == -1) 554 { 555 //writefln("level == -1"); 556 sel--; 557 if (sel < 0) 558 { 559 //writefln("sel = 0"); 560 sel = selections.length-1; 561 seln_count++; 562 } 563 } 564 } 565 } while (level_dec && level >= 0); 566 } 567 else if (sel == -1) 568 { 569 sel = selections.length-1; 570 seln_count++; 571 } 572 573 //writefln("level = %s", level); 574 if (level == -1) 575 { 576 string[] paths; 577 try 578 { 579 foreach (string name; dirEntries(selections[sel], SpanMode.shallow)) 580 { 581 paths ~= name; 582 } 583 } 584 catch (FileException e) 585 { 586 return; 587 } 588 589 sort!("a < b")(paths); 590 591 positions ~= paths.length-1; 592 files ~= paths; 593 level++; 594 //writefln("0 paths added"); 595 } 596 597 do 598 { 599 if (files[level].length == 0) break; 600 string p = files[level][positions[level]]; 601 //writefln("p=%s", p); 602 string mime_type = mime(p); 603 if (mime_type == "inode/directory") 604 { 605 //writefln("directory"); 606 string[] paths; 607 608 try 609 { 610 foreach (string name; dirEntries(p, SpanMode.shallow)) 611 { 612 paths ~= name; 613 } 614 } 615 catch (FileException e) 616 { 617 return; 618 } 619 620 if (paths.length == 0) 621 { 622 break; 623 } 624 625 sort!("a < b")(paths); 626 627 positions ~= paths.length-1; 628 files ~= paths; 629 level++; 630 } 631 else if (mime_type.startsWith("image/")) 632 { 633 //writefln("image!"); 634 auto pathmnt = PathMnt(gs.lsblk, p); 635 image_viewer(gs, pathmnt); 636 found_image = true; 637 break; 638 } 639 else 640 { 641 break; 642 } 643 } while (true); 644 } while(seln_count < 2 && !found_image); 645 } 646 }