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 }