1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2008 Blender Foundation.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup spfile
22  */
23 
24 #include <math.h>
25 #include <stdio.h>
26 #include <string.h>
27 
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 
31 /* path/file handling stuff */
32 #ifdef WIN32
33 #  include "BLI_winstuff.h"
34 #  include <direct.h>
35 #  include <io.h>
36 #else
37 #  include <dirent.h>
38 #  include <sys/times.h>
39 #  include <unistd.h>
40 #endif
41 
42 #include "DNA_screen_types.h"
43 #include "DNA_space_types.h"
44 #include "DNA_userdef_types.h"
45 
46 #include "MEM_guardedalloc.h"
47 
48 #include "BLI_blenlib.h"
49 #include "BLI_fnmatch.h"
50 #include "BLI_math_base.h"
51 #include "BLI_utildefines.h"
52 
53 #include "BLO_readfile.h"
54 
55 #include "BLT_translation.h"
56 
57 #include "BKE_appdir.h"
58 #include "BKE_context.h"
59 #include "BKE_main.h"
60 
61 #include "BLF_api.h"
62 
63 #include "ED_fileselect.h"
64 
65 #include "WM_api.h"
66 #include "WM_types.h"
67 
68 #include "RNA_access.h"
69 
70 #include "UI_interface.h"
71 #include "UI_interface_icons.h"
72 #include "UI_view2d.h"
73 
74 #include "file_intern.h"
75 #include "filelist.h"
76 
77 #define VERTLIST_MAJORCOLUMN_WIDTH (25 * UI_UNIT_X)
78 
ED_fileselect_get_params(struct SpaceFile * sfile)79 FileSelectParams *ED_fileselect_get_params(struct SpaceFile *sfile)
80 {
81   if (!sfile->params) {
82     ED_fileselect_set_params(sfile);
83   }
84   return sfile->params;
85 }
86 
87 /**
88  * \note RNA_struct_property_is_set_ex is used here because we want
89  *       the previously used settings to be used here rather than overriding them */
ED_fileselect_set_params(SpaceFile * sfile)90 short ED_fileselect_set_params(SpaceFile *sfile)
91 {
92   FileSelectParams *params;
93   wmOperator *op = sfile->op;
94 
95   const char *blendfile_path = BKE_main_blendfile_path_from_global();
96 
97   /* create new parameters if necessary */
98   if (!sfile->params) {
99     sfile->params = MEM_callocN(sizeof(FileSelectParams), "fileselparams");
100     /* set path to most recently opened .blend */
101     BLI_split_dirfile(blendfile_path,
102                       sfile->params->dir,
103                       sfile->params->file,
104                       sizeof(sfile->params->dir),
105                       sizeof(sfile->params->file));
106     sfile->params->filter_glob[0] = '\0';
107     sfile->params->thumbnail_size = U_default.file_space_data.thumbnail_size;
108     sfile->params->details_flags = U_default.file_space_data.details_flags;
109     sfile->params->filter_id = U_default.file_space_data.filter_id;
110   }
111 
112   params = sfile->params;
113 
114   /* set the parameters from the operator, if it exists */
115   if (op) {
116     PropertyRNA *prop;
117     const bool is_files = (RNA_struct_find_property(op->ptr, "files") != NULL);
118     const bool is_filepath = (RNA_struct_find_property(op->ptr, "filepath") != NULL);
119     const bool is_filename = (RNA_struct_find_property(op->ptr, "filename") != NULL);
120     const bool is_directory = (RNA_struct_find_property(op->ptr, "directory") != NULL);
121     const bool is_relative_path = (RNA_struct_find_property(op->ptr, "relative_path") != NULL);
122 
123     BLI_strncpy_utf8(
124         params->title, WM_operatortype_name(op->type, op->ptr), sizeof(params->title));
125 
126     if ((prop = RNA_struct_find_property(op->ptr, "filemode"))) {
127       params->type = RNA_property_int_get(op->ptr, prop);
128     }
129     else {
130       params->type = FILE_SPECIAL;
131     }
132 
133     if (is_filepath && RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
134       char name[FILE_MAX];
135       RNA_string_get(op->ptr, "filepath", name);
136       if (params->type == FILE_LOADLIB) {
137         BLI_strncpy(params->dir, name, sizeof(params->dir));
138         sfile->params->file[0] = '\0';
139       }
140       else {
141         BLI_split_dirfile(name,
142                           sfile->params->dir,
143                           sfile->params->file,
144                           sizeof(sfile->params->dir),
145                           sizeof(sfile->params->file));
146       }
147     }
148     else {
149       if (is_directory && RNA_struct_property_is_set_ex(op->ptr, "directory", false)) {
150         RNA_string_get(op->ptr, "directory", params->dir);
151         sfile->params->file[0] = '\0';
152       }
153 
154       if (is_filename && RNA_struct_property_is_set_ex(op->ptr, "filename", false)) {
155         RNA_string_get(op->ptr, "filename", params->file);
156       }
157     }
158 
159     if (params->dir[0]) {
160       BLI_path_normalize_dir(blendfile_path, params->dir);
161       BLI_path_abs(params->dir, blendfile_path);
162     }
163 
164     params->flag = 0;
165     if (is_directory == true && is_filename == false && is_filepath == false &&
166         is_files == false) {
167       params->flag |= FILE_DIRSEL_ONLY;
168     }
169     if ((prop = RNA_struct_find_property(op->ptr, "check_existing"))) {
170       params->flag |= RNA_property_boolean_get(op->ptr, prop) ? FILE_CHECK_EXISTING : 0;
171     }
172     if ((prop = RNA_struct_find_property(op->ptr, "hide_props_region"))) {
173       params->flag |= RNA_property_boolean_get(op->ptr, prop) ? FILE_HIDE_TOOL_PROPS : 0;
174     }
175 
176     params->filter = 0;
177     if ((prop = RNA_struct_find_property(op->ptr, "filter_blender"))) {
178       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BLENDER : 0;
179     }
180     if ((prop = RNA_struct_find_property(op->ptr, "filter_blenlib"))) {
181       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BLENDERLIB : 0;
182     }
183     if ((prop = RNA_struct_find_property(op->ptr, "filter_backup"))) {
184       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BLENDER_BACKUP : 0;
185     }
186     if ((prop = RNA_struct_find_property(op->ptr, "filter_image"))) {
187       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_IMAGE : 0;
188     }
189     if ((prop = RNA_struct_find_property(op->ptr, "filter_movie"))) {
190       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_MOVIE : 0;
191     }
192     if ((prop = RNA_struct_find_property(op->ptr, "filter_python"))) {
193       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_PYSCRIPT : 0;
194     }
195     if ((prop = RNA_struct_find_property(op->ptr, "filter_font"))) {
196       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_FTFONT : 0;
197     }
198     if ((prop = RNA_struct_find_property(op->ptr, "filter_sound"))) {
199       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_SOUND : 0;
200     }
201     if ((prop = RNA_struct_find_property(op->ptr, "filter_text"))) {
202       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_TEXT : 0;
203     }
204     if ((prop = RNA_struct_find_property(op->ptr, "filter_archive"))) {
205       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_ARCHIVE : 0;
206     }
207     if ((prop = RNA_struct_find_property(op->ptr, "filter_folder"))) {
208       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_FOLDER : 0;
209     }
210     if ((prop = RNA_struct_find_property(op->ptr, "filter_btx"))) {
211       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BTX : 0;
212     }
213     if ((prop = RNA_struct_find_property(op->ptr, "filter_collada"))) {
214       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_COLLADA : 0;
215     }
216     if ((prop = RNA_struct_find_property(op->ptr, "filter_alembic"))) {
217       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_ALEMBIC : 0;
218     }
219     if ((prop = RNA_struct_find_property(op->ptr, "filter_usd"))) {
220       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_USD : 0;
221     }
222     if ((prop = RNA_struct_find_property(op->ptr, "filter_volume"))) {
223       params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_VOLUME : 0;
224     }
225     if ((prop = RNA_struct_find_property(op->ptr, "filter_glob"))) {
226       /* Protection against pyscripts not setting proper size limit... */
227       char *tmp = RNA_property_string_get_alloc(
228           op->ptr, prop, params->filter_glob, sizeof(params->filter_glob), NULL);
229       if (tmp != params->filter_glob) {
230         BLI_strncpy(params->filter_glob, tmp, sizeof(params->filter_glob));
231         MEM_freeN(tmp);
232 
233         /* Fix stupid things that truncating might have generated,
234          * like last group being a 'match everything' wildcard-only one... */
235         BLI_path_extension_glob_validate(params->filter_glob);
236       }
237       params->filter |= (FILE_TYPE_OPERATOR | FILE_TYPE_FOLDER);
238     }
239     else {
240       params->filter_glob[0] = '\0';
241     }
242 
243     if (params->filter != 0) {
244       if (U.uiflag & USER_FILTERFILEEXTS) {
245         params->flag |= FILE_FILTER;
246       }
247       else {
248         params->flag &= ~FILE_FILTER;
249       }
250     }
251 
252     if (U.uiflag & USER_HIDE_DOT) {
253       params->flag |= FILE_HIDE_DOT;
254     }
255     else {
256       params->flag &= ~FILE_HIDE_DOT;
257     }
258 
259     if (params->type == FILE_LOADLIB) {
260       params->flag |= RNA_boolean_get(op->ptr, "link") ? FILE_LINK : 0;
261       params->flag |= RNA_boolean_get(op->ptr, "autoselect") ? FILE_AUTOSELECT : 0;
262       params->flag |= RNA_boolean_get(op->ptr, "active_collection") ? FILE_ACTIVE_COLLECTION : 0;
263     }
264 
265     if ((prop = RNA_struct_find_property(op->ptr, "display_type"))) {
266       params->display = RNA_property_enum_get(op->ptr, prop);
267     }
268 
269     if ((prop = RNA_struct_find_property(op->ptr, "sort_method"))) {
270       params->sort = RNA_property_enum_get(op->ptr, prop);
271     }
272     else {
273       params->sort = U_default.file_space_data.sort_type;
274     }
275 
276     if (params->display == FILE_DEFAULTDISPLAY) {
277       params->display = U_default.file_space_data.display_type;
278     }
279 
280     if (is_relative_path) {
281       if ((prop = RNA_struct_find_property(op->ptr, "relative_path"))) {
282         if (!RNA_property_is_set_ex(op->ptr, prop, false)) {
283           RNA_property_boolean_set(op->ptr, prop, (U.flag & USER_RELPATHS) != 0);
284         }
285       }
286     }
287   }
288   else {
289     /* default values, if no operator */
290     params->type = FILE_UNIX;
291     params->flag |= U_default.file_space_data.flag;
292     params->flag &= ~FILE_DIRSEL_ONLY;
293     params->display = FILE_VERTICALDISPLAY;
294     params->sort = FILE_SORT_ALPHA;
295     params->filter = 0;
296     params->filter_glob[0] = '\0';
297   }
298 
299   /* operator has no setting for this */
300   params->active_file = -1;
301 
302   /* initialize the list with previous folders */
303   if (!sfile->folders_prev) {
304     sfile->folders_prev = folderlist_new();
305   }
306 
307   if (!sfile->params->dir[0]) {
308     if (blendfile_path[0] != '\0') {
309       BLI_split_dir_part(blendfile_path, sfile->params->dir, sizeof(sfile->params->dir));
310     }
311     else {
312       const char *doc_path = BKE_appdir_folder_default();
313       if (doc_path) {
314         BLI_strncpy(sfile->params->dir, doc_path, sizeof(sfile->params->dir));
315       }
316     }
317   }
318 
319   folderlist_pushdir(sfile->folders_prev, sfile->params->dir);
320 
321   /* Switching thumbnails needs to recalc layout T28809. */
322   if (sfile->layout) {
323     sfile->layout->dirty = true;
324   }
325 
326   return 1;
327 }
328 
329 /* The subset of FileSelectParams.flag items we store into preferences. */
330 #define PARAMS_FLAGS_REMEMBERED (FILE_HIDE_DOT | FILE_SORT_INVERT)
331 
ED_fileselect_window_params_get(const wmWindow * win,int win_size[2],bool * is_maximized)332 void ED_fileselect_window_params_get(const wmWindow *win, int win_size[2], bool *is_maximized)
333 {
334   /* Get DPI/pixelsize independent size to be stored in preferences. */
335   WM_window_set_dpi(win); /* Ensure the DPI is taken from the right window. */
336 
337   win_size[0] = WM_window_pixels_x(win) / UI_DPI_FAC;
338   win_size[1] = WM_window_pixels_y(win) / UI_DPI_FAC;
339 
340   *is_maximized = WM_window_is_maximized(win);
341 }
342 
ED_fileselect_set_params_from_userdef(SpaceFile * sfile)343 void ED_fileselect_set_params_from_userdef(SpaceFile *sfile)
344 {
345   wmOperator *op = sfile->op;
346   UserDef_FileSpaceData *sfile_udata = &U.file_space_data;
347 
348   ED_fileselect_set_params(sfile);
349 
350   if (!op) {
351     return;
352   }
353 
354   if (!RNA_struct_property_is_set(op->ptr, "display_type")) {
355     sfile->params->display = sfile_udata->display_type;
356   }
357   if (!RNA_struct_property_is_set(op->ptr, "sort_method")) {
358     sfile->params->sort = sfile_udata->sort_type;
359   }
360   sfile->params->thumbnail_size = sfile_udata->thumbnail_size;
361   sfile->params->details_flags = sfile_udata->details_flags;
362   sfile->params->filter_id = sfile_udata->filter_id;
363 
364   /* Combine flags we take from params with the flags we take from userdef. */
365   sfile->params->flag = (sfile->params->flag & ~PARAMS_FLAGS_REMEMBERED) |
366                         (sfile_udata->flag & PARAMS_FLAGS_REMEMBERED);
367 }
368 
369 /**
370  * Update the user-preference data for the file space. In fact, this also contains some
371  * non-FileSelectParams data, but we can safely ignore this.
372  *
373  * \param temp_win_size: If the browser was opened in a temporary window,
374  * pass its size here so we can store that in the preferences. Otherwise NULL.
375  */
ED_fileselect_params_to_userdef(SpaceFile * sfile,const int temp_win_size[2],const bool is_maximized)376 void ED_fileselect_params_to_userdef(SpaceFile *sfile,
377                                      const int temp_win_size[2],
378                                      const bool is_maximized)
379 {
380   UserDef_FileSpaceData *sfile_udata_new = &U.file_space_data;
381   UserDef_FileSpaceData sfile_udata_old = U.file_space_data;
382 
383   sfile_udata_new->display_type = sfile->params->display;
384   sfile_udata_new->thumbnail_size = sfile->params->thumbnail_size;
385   sfile_udata_new->sort_type = sfile->params->sort;
386   sfile_udata_new->details_flags = sfile->params->details_flags;
387   sfile_udata_new->flag = sfile->params->flag & PARAMS_FLAGS_REMEMBERED;
388   sfile_udata_new->filter_id = sfile->params->filter_id;
389 
390   if (temp_win_size && !is_maximized) {
391     sfile_udata_new->temp_win_sizex = temp_win_size[0];
392     sfile_udata_new->temp_win_sizey = temp_win_size[1];
393   }
394 
395   /* Tag prefs as dirty if something has changed. */
396   if (memcmp(sfile_udata_new, &sfile_udata_old, sizeof(sfile_udata_old)) != 0) {
397     U.runtime.is_dirty = true;
398   }
399 }
400 
ED_fileselect_reset_params(SpaceFile * sfile)401 void ED_fileselect_reset_params(SpaceFile *sfile)
402 {
403   sfile->params->type = FILE_UNIX;
404   sfile->params->flag = 0;
405   sfile->params->title[0] = '\0';
406   sfile->params->active_file = -1;
407 }
408 
409 /**
410  * Sets FileSelectParams->file (name of selected file)
411  */
fileselect_file_set(SpaceFile * sfile,const int index)412 void fileselect_file_set(SpaceFile *sfile, const int index)
413 {
414   const struct FileDirEntry *file = filelist_file(sfile->files, index);
415   if (file && file->relpath && file->relpath[0] && !(file->typeflag & FILE_TYPE_DIR)) {
416     BLI_strncpy(sfile->params->file, file->relpath, FILE_MAXFILE);
417   }
418 }
419 
ED_fileselect_layout_numfiles(FileLayout * layout,ARegion * region)420 int ED_fileselect_layout_numfiles(FileLayout *layout, ARegion *region)
421 {
422   int numfiles;
423 
424   /* Values in pixels.
425    *
426    * - *_item: size of each (row|col), (including padding)
427    * - *_view: (x|y) size of the view.
428    * - *_over: extra pixels, to take into account, when the fit isnt exact
429    *   (needed since you may see the end of the previous column and the beginning of the next).
430    *
431    * Could be more clever and take scrolling into account,
432    * but for now don't bother.
433    */
434   if (layout->flag & FILE_LAYOUT_HOR) {
435     const int x_item = layout->tile_w + (2 * layout->tile_border_x);
436     const int x_view = (int)(BLI_rctf_size_x(&region->v2d.cur));
437     const int x_over = x_item - (x_view % x_item);
438     numfiles = (int)((float)(x_view + x_over) / (float)(x_item));
439     return numfiles * layout->rows;
440   }
441 
442   const int y_item = layout->tile_h + (2 * layout->tile_border_y);
443   const int y_view = (int)(BLI_rctf_size_y(&region->v2d.cur)) - layout->offset_top;
444   const int y_over = y_item - (y_view % y_item);
445   numfiles = (int)((float)(y_view + y_over) / (float)(y_item));
446   return numfiles * layout->flow_columns;
447 }
448 
is_inside(int x,int y,int cols,int rows)449 static bool is_inside(int x, int y, int cols, int rows)
450 {
451   return ((x >= 0) && (x < cols) && (y >= 0) && (y < rows));
452 }
453 
ED_fileselect_layout_offset_rect(FileLayout * layout,const rcti * rect)454 FileSelection ED_fileselect_layout_offset_rect(FileLayout *layout, const rcti *rect)
455 {
456   int colmin, colmax, rowmin, rowmax;
457   FileSelection sel;
458   sel.first = sel.last = -1;
459 
460   if (layout == NULL) {
461     return sel;
462   }
463 
464   colmin = (rect->xmin) / (layout->tile_w + 2 * layout->tile_border_x);
465   rowmin = (rect->ymin - layout->offset_top) / (layout->tile_h + 2 * layout->tile_border_y);
466   colmax = (rect->xmax) / (layout->tile_w + 2 * layout->tile_border_x);
467   rowmax = (rect->ymax - layout->offset_top) / (layout->tile_h + 2 * layout->tile_border_y);
468 
469   if (is_inside(colmin, rowmin, layout->flow_columns, layout->rows) ||
470       is_inside(colmax, rowmax, layout->flow_columns, layout->rows)) {
471     CLAMP(colmin, 0, layout->flow_columns - 1);
472     CLAMP(rowmin, 0, layout->rows - 1);
473     CLAMP(colmax, 0, layout->flow_columns - 1);
474     CLAMP(rowmax, 0, layout->rows - 1);
475   }
476 
477   if ((colmin > layout->flow_columns - 1) || (rowmin > layout->rows - 1)) {
478     sel.first = -1;
479   }
480   else {
481     if (layout->flag & FILE_LAYOUT_HOR) {
482       sel.first = layout->rows * colmin + rowmin;
483     }
484     else {
485       sel.first = colmin + layout->flow_columns * rowmin;
486     }
487   }
488   if ((colmax > layout->flow_columns - 1) || (rowmax > layout->rows - 1)) {
489     sel.last = -1;
490   }
491   else {
492     if (layout->flag & FILE_LAYOUT_HOR) {
493       sel.last = layout->rows * colmax + rowmax;
494     }
495     else {
496       sel.last = colmax + layout->flow_columns * rowmax;
497     }
498   }
499 
500   return sel;
501 }
502 
ED_fileselect_layout_offset(FileLayout * layout,int x,int y)503 int ED_fileselect_layout_offset(FileLayout *layout, int x, int y)
504 {
505   int offsetx, offsety;
506   int active_file;
507 
508   if (layout == NULL) {
509     return -1;
510   }
511 
512   offsetx = (x) / (layout->tile_w + 2 * layout->tile_border_x);
513   offsety = (y - layout->offset_top) / (layout->tile_h + 2 * layout->tile_border_y);
514 
515   if (offsetx > layout->flow_columns - 1) {
516     return -1;
517   }
518   if (offsety > layout->rows - 1) {
519     return -1;
520   }
521 
522   if (layout->flag & FILE_LAYOUT_HOR) {
523     active_file = layout->rows * offsetx + offsety;
524   }
525   else {
526     active_file = offsetx + layout->flow_columns * offsety;
527   }
528   return active_file;
529 }
530 
531 /**
532  * Get the currently visible bounds of the layout in screen space. Matches View2D.mask minus the
533  * top column-header row.
534  */
ED_fileselect_layout_maskrect(const FileLayout * layout,const View2D * v2d,rcti * r_rect)535 void ED_fileselect_layout_maskrect(const FileLayout *layout, const View2D *v2d, rcti *r_rect)
536 {
537   *r_rect = v2d->mask;
538   r_rect->ymax -= layout->offset_top;
539 }
540 
ED_fileselect_layout_is_inside_pt(const FileLayout * layout,const View2D * v2d,int x,int y)541 bool ED_fileselect_layout_is_inside_pt(const FileLayout *layout, const View2D *v2d, int x, int y)
542 {
543   rcti maskrect;
544   ED_fileselect_layout_maskrect(layout, v2d, &maskrect);
545   return BLI_rcti_isect_pt(&maskrect, x, y);
546 }
547 
ED_fileselect_layout_isect_rect(const FileLayout * layout,const View2D * v2d,const rcti * rect,rcti * r_dst)548 bool ED_fileselect_layout_isect_rect(const FileLayout *layout,
549                                      const View2D *v2d,
550                                      const rcti *rect,
551                                      rcti *r_dst)
552 {
553   rcti maskrect;
554   ED_fileselect_layout_maskrect(layout, v2d, &maskrect);
555   return BLI_rcti_isect(&maskrect, rect, r_dst);
556 }
557 
ED_fileselect_layout_tilepos(FileLayout * layout,int tile,int * x,int * y)558 void ED_fileselect_layout_tilepos(FileLayout *layout, int tile, int *x, int *y)
559 {
560   if (layout->flag == FILE_LAYOUT_HOR) {
561     *x = layout->tile_border_x +
562          (tile / layout->rows) * (layout->tile_w + 2 * layout->tile_border_x);
563     *y = layout->offset_top + layout->tile_border_y +
564          (tile % layout->rows) * (layout->tile_h + 2 * layout->tile_border_y);
565   }
566   else {
567     *x = layout->tile_border_x +
568          ((tile) % layout->flow_columns) * (layout->tile_w + 2 * layout->tile_border_x);
569     *y = layout->offset_top + layout->tile_border_y +
570          ((tile) / layout->flow_columns) * (layout->tile_h + 2 * layout->tile_border_y);
571   }
572 }
573 
574 /**
575  * Check if the region coordinate defined by \a x and \a y are inside the column header.
576  */
file_attribute_column_header_is_inside(const View2D * v2d,const FileLayout * layout,int x,int y)577 bool file_attribute_column_header_is_inside(const View2D *v2d,
578                                             const FileLayout *layout,
579                                             int x,
580                                             int y)
581 {
582   rcti header_rect = v2d->mask;
583   header_rect.ymin = header_rect.ymax - layout->attribute_column_header_h;
584   return BLI_rcti_isect_pt(&header_rect, x, y);
585 }
586 
file_attribute_column_type_enabled(const FileSelectParams * params,FileAttributeColumnType column)587 bool file_attribute_column_type_enabled(const FileSelectParams *params,
588                                         FileAttributeColumnType column)
589 {
590   switch (column) {
591     case COLUMN_NAME:
592       /* Always enabled */
593       return true;
594     case COLUMN_DATETIME:
595       return (params->details_flags & FILE_DETAILS_DATETIME) != 0;
596     case COLUMN_SIZE:
597       return (params->details_flags & FILE_DETAILS_SIZE) != 0;
598     default:
599       return false;
600   }
601 }
602 
603 /**
604  * Find the column type at region coordinate given by \a x (y doesn't matter for this).
605  */
file_attribute_column_type_find_isect(const View2D * v2d,const FileSelectParams * params,FileLayout * layout,int x)606 FileAttributeColumnType file_attribute_column_type_find_isect(const View2D *v2d,
607                                                               const FileSelectParams *params,
608                                                               FileLayout *layout,
609                                                               int x)
610 {
611   float mx, my;
612   int offset_tile;
613 
614   UI_view2d_region_to_view(v2d, x, v2d->mask.ymax - layout->offset_top - 1, &mx, &my);
615   offset_tile = ED_fileselect_layout_offset(
616       layout, (int)(v2d->tot.xmin + mx), (int)(v2d->tot.ymax - my));
617   if (offset_tile > -1) {
618     int tile_x, tile_y;
619     int pos_x = 0;
620     int rel_x; /* x relative to the hovered tile */
621 
622     ED_fileselect_layout_tilepos(layout, offset_tile, &tile_x, &tile_y);
623     /* Column header drawing doesn't use left tile border, so subtract it. */
624     rel_x = mx - (tile_x - layout->tile_border_x);
625 
626     for (FileAttributeColumnType column = 0; column < ATTRIBUTE_COLUMN_MAX; column++) {
627       if (!file_attribute_column_type_enabled(params, column)) {
628         continue;
629       }
630       const int width = layout->attribute_columns[column].width;
631 
632       if (IN_RANGE(rel_x, pos_x, pos_x + width)) {
633         return column;
634       }
635 
636       pos_x += width;
637     }
638   }
639 
640   return COLUMN_NONE;
641 }
642 
file_string_width(const char * str)643 float file_string_width(const char *str)
644 {
645   const uiStyle *style = UI_style_get();
646   float width;
647 
648   UI_fontstyle_set(&style->widget);
649   if (style->widget.kerning == 1) { /* for BLF_width */
650     BLF_enable(style->widget.uifont_id, BLF_KERNING_DEFAULT);
651   }
652 
653   width = BLF_width(style->widget.uifont_id, str, BLF_DRAW_STR_DUMMY_MAX);
654 
655   if (style->widget.kerning == 1) {
656     BLF_disable(style->widget.uifont_id, BLF_KERNING_DEFAULT);
657   }
658 
659   return width;
660 }
661 
file_font_pointsize(void)662 float file_font_pointsize(void)
663 {
664 #if 0
665   float s;
666   char tmp[2] = "X";
667   const uiStyle *style = UI_style_get();
668   UI_fontstyle_set(&style->widget);
669   s = BLF_height(style->widget.uifont_id, tmp);
670   return style->widget.points;
671 #else
672   const uiStyle *style = UI_style_get();
673   UI_fontstyle_set(&style->widget);
674   return style->widget.points * UI_DPI_FAC;
675 #endif
676 }
677 
file_attribute_columns_widths(const FileSelectParams * params,FileLayout * layout)678 static void file_attribute_columns_widths(const FileSelectParams *params, FileLayout *layout)
679 {
680   FileAttributeColumn *columns = layout->attribute_columns;
681   const bool small_size = SMALL_SIZE_CHECK(params->thumbnail_size);
682   const int pad = small_size ? 0 : ATTRIBUTE_COLUMN_PADDING * 2;
683 
684   for (int i = 0; i < ATTRIBUTE_COLUMN_MAX; i++) {
685     layout->attribute_columns[i].width = 0;
686   }
687 
688   /* Biggest possible reasonable values... */
689   columns[COLUMN_DATETIME].width = file_string_width(small_size ? "23/08/89" :
690                                                                   "23 Dec 6789, 23:59") +
691                                    pad;
692   columns[COLUMN_SIZE].width = file_string_width(small_size ? "98.7 M" : "098.7 MiB") + pad;
693   if (params->display == FILE_IMGDISPLAY) {
694     columns[COLUMN_NAME].width = ((float)params->thumbnail_size / 8.0f) * UI_UNIT_X;
695   }
696   /* Name column uses remaining width */
697   else {
698     int remwidth = layout->tile_w;
699     for (FileAttributeColumnType column_type = ATTRIBUTE_COLUMN_MAX - 1; column_type >= 0;
700          column_type--) {
701       if ((column_type == COLUMN_NAME) ||
702           !file_attribute_column_type_enabled(params, column_type)) {
703         continue;
704       }
705       remwidth -= columns[column_type].width;
706     }
707     columns[COLUMN_NAME].width = remwidth;
708   }
709 }
710 
file_attribute_columns_init(const FileSelectParams * params,FileLayout * layout)711 static void file_attribute_columns_init(const FileSelectParams *params, FileLayout *layout)
712 {
713   file_attribute_columns_widths(params, layout);
714 
715   layout->attribute_columns[COLUMN_NAME].name = N_("Name");
716   layout->attribute_columns[COLUMN_NAME].sort_type = FILE_SORT_ALPHA;
717   layout->attribute_columns[COLUMN_NAME].text_align = UI_STYLE_TEXT_LEFT;
718   layout->attribute_columns[COLUMN_DATETIME].name = N_("Date Modified");
719   layout->attribute_columns[COLUMN_DATETIME].sort_type = FILE_SORT_TIME;
720   layout->attribute_columns[COLUMN_DATETIME].text_align = UI_STYLE_TEXT_LEFT;
721   layout->attribute_columns[COLUMN_SIZE].name = N_("Size");
722   layout->attribute_columns[COLUMN_SIZE].sort_type = FILE_SORT_SIZE;
723   layout->attribute_columns[COLUMN_SIZE].text_align = UI_STYLE_TEXT_RIGHT;
724 }
725 
ED_fileselect_init_layout(struct SpaceFile * sfile,ARegion * region)726 void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *region)
727 {
728   FileSelectParams *params = ED_fileselect_get_params(sfile);
729   FileLayout *layout = NULL;
730   View2D *v2d = &region->v2d;
731   int numfiles;
732   int textheight;
733 
734   if (sfile->layout == NULL) {
735     sfile->layout = MEM_callocN(sizeof(struct FileLayout), "file_layout");
736     sfile->layout->dirty = true;
737   }
738   else if (sfile->layout->dirty == false) {
739     return;
740   }
741 
742   numfiles = filelist_files_ensure(sfile->files);
743   textheight = (int)file_font_pointsize();
744   layout = sfile->layout;
745   layout->textheight = textheight;
746 
747   if (params->display == FILE_IMGDISPLAY) {
748     layout->prv_w = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_X;
749     layout->prv_h = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_Y;
750     layout->tile_border_x = 0.3f * UI_UNIT_X;
751     layout->tile_border_y = 0.3f * UI_UNIT_X;
752     layout->prv_border_x = 0.3f * UI_UNIT_X;
753     layout->prv_border_y = 0.3f * UI_UNIT_Y;
754     layout->tile_w = layout->prv_w + 2 * layout->prv_border_x;
755     layout->tile_h = layout->prv_h + 2 * layout->prv_border_y + textheight;
756     layout->width = (int)(BLI_rctf_size_x(&v2d->cur) - 2 * layout->tile_border_x);
757     layout->flow_columns = layout->width / (layout->tile_w + 2 * layout->tile_border_x);
758     layout->attribute_column_header_h = 0;
759     layout->offset_top = 0;
760     if (layout->flow_columns > 0) {
761       layout->rows = divide_ceil_u(numfiles, layout->flow_columns);
762     }
763     else {
764       layout->flow_columns = 1;
765       layout->rows = numfiles;
766     }
767     layout->height = sfile->layout->rows * (layout->tile_h + 2 * layout->tile_border_y) +
768                      layout->tile_border_y * 2 - layout->offset_top;
769     layout->flag = FILE_LAYOUT_VER;
770   }
771   else if (params->display == FILE_VERTICALDISPLAY) {
772     int rowcount;
773 
774     layout->prv_w = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_X;
775     layout->prv_h = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_Y;
776     layout->tile_border_x = 0.4f * UI_UNIT_X;
777     layout->tile_border_y = 0.1f * UI_UNIT_Y;
778     layout->tile_h = textheight * 3 / 2;
779     layout->width = (int)(BLI_rctf_size_x(&v2d->cur) - 2 * layout->tile_border_x);
780     layout->tile_w = layout->width;
781     layout->flow_columns = 1;
782     layout->attribute_column_header_h = layout->tile_h * 1.2f + 2 * layout->tile_border_y;
783     layout->offset_top = layout->attribute_column_header_h;
784     rowcount = (int)(BLI_rctf_size_y(&v2d->cur) - layout->offset_top - 2 * layout->tile_border_y) /
785                (layout->tile_h + 2 * layout->tile_border_y);
786     file_attribute_columns_init(params, layout);
787 
788     layout->rows = MAX2(rowcount, numfiles);
789     BLI_assert(layout->rows != 0);
790     layout->height = sfile->layout->rows * (layout->tile_h + 2 * layout->tile_border_y) +
791                      layout->tile_border_y * 2 + layout->offset_top;
792     layout->flag = FILE_LAYOUT_VER;
793   }
794   else if (params->display == FILE_HORIZONTALDISPLAY) {
795     layout->prv_w = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_X;
796     layout->prv_h = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_Y;
797     layout->tile_border_x = 0.4f * UI_UNIT_X;
798     layout->tile_border_y = 0.1f * UI_UNIT_Y;
799     layout->tile_h = textheight * 3 / 2;
800     layout->attribute_column_header_h = 0;
801     layout->offset_top = layout->attribute_column_header_h;
802     layout->height = (int)(BLI_rctf_size_y(&v2d->cur) - 2 * layout->tile_border_y);
803     /* Padding by full scrollbar H is too much, can overlap tile border Y. */
804     layout->rows = (layout->height - V2D_SCROLL_HEIGHT + layout->tile_border_y) /
805                    (layout->tile_h + 2 * layout->tile_border_y);
806     layout->tile_w = VERTLIST_MAJORCOLUMN_WIDTH;
807     file_attribute_columns_init(params, layout);
808 
809     if (layout->rows > 0) {
810       layout->flow_columns = divide_ceil_u(numfiles, layout->rows);
811     }
812     else {
813       layout->rows = 1;
814       layout->flow_columns = numfiles;
815     }
816     layout->width = sfile->layout->flow_columns * (layout->tile_w + 2 * layout->tile_border_x) +
817                     layout->tile_border_x * 2;
818     layout->flag = FILE_LAYOUT_HOR;
819   }
820   layout->dirty = false;
821 }
822 
ED_fileselect_get_layout(struct SpaceFile * sfile,ARegion * region)823 FileLayout *ED_fileselect_get_layout(struct SpaceFile *sfile, ARegion *region)
824 {
825   if (!sfile->layout) {
826     ED_fileselect_init_layout(sfile, region);
827   }
828   return sfile->layout;
829 }
830 
831 /**
832  * Support updating the directory even when this isn't the active space
833  * needed so RNA properties update function isn't context sensitive, see T70255.
834  */
ED_file_change_dir_ex(bContext * C,bScreen * screen,ScrArea * area)835 void ED_file_change_dir_ex(bContext *C, bScreen *screen, ScrArea *area)
836 {
837   /* May happen when manipulating non-active spaces. */
838   if (UNLIKELY(area->spacetype != SPACE_FILE)) {
839     return;
840   }
841   SpaceFile *sfile = area->spacedata.first;
842   if (sfile->params) {
843     wmWindowManager *wm = CTX_wm_manager(C);
844     Scene *scene = WM_windows_scene_get_from_screen(wm, screen);
845     if (LIKELY(scene != NULL)) {
846       ED_fileselect_clear(wm, scene, sfile);
847     }
848 
849     /* Clear search string, it is very rare to want to keep that filter while changing dir,
850      * and usually very annoying to keep it actually! */
851     sfile->params->filter_search[0] = '\0';
852     sfile->params->active_file = -1;
853 
854     if (!filelist_is_dir(sfile->files, sfile->params->dir)) {
855       BLI_strncpy(sfile->params->dir, filelist_dir(sfile->files), sizeof(sfile->params->dir));
856       /* could return but just refresh the current dir */
857     }
858     filelist_setdir(sfile->files, sfile->params->dir);
859 
860     if (folderlist_clear_next(sfile)) {
861       folderlist_free(sfile->folders_next);
862     }
863 
864     folderlist_pushdir(sfile->folders_prev, sfile->params->dir);
865 
866     file_draw_check_ex(C, area);
867   }
868 }
869 
ED_file_change_dir(bContext * C)870 void ED_file_change_dir(bContext *C)
871 {
872   bScreen *screen = CTX_wm_screen(C);
873   ScrArea *area = CTX_wm_area(C);
874   ED_file_change_dir_ex(C, screen, area);
875 }
876 
file_select_match(struct SpaceFile * sfile,const char * pattern,char * matched_file)877 int file_select_match(struct SpaceFile *sfile, const char *pattern, char *matched_file)
878 {
879   int match = 0;
880 
881   int n = filelist_files_ensure(sfile->files);
882 
883   /* select any file that matches the pattern, this includes exact match
884    * if the user selects a single file by entering the filename
885    */
886   for (int i = 0; i < n; i++) {
887     FileDirEntry *file = filelist_file(sfile->files, i);
888     /* Do not check whether file is a file or dir here! Causes T44243
889      * (we do accept dirs at this stage). */
890     if (fnmatch(pattern, file->relpath, 0) == 0) {
891       filelist_entry_select_set(sfile->files, file, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL);
892       if (!match) {
893         BLI_strncpy(matched_file, file->relpath, FILE_MAX);
894       }
895       match++;
896     }
897   }
898 
899   return match;
900 }
901 
autocomplete_directory(struct bContext * C,char * str,void * UNUSED (arg_v))902 int autocomplete_directory(struct bContext *C, char *str, void *UNUSED(arg_v))
903 {
904   SpaceFile *sfile = CTX_wm_space_file(C);
905   int match = AUTOCOMPLETE_NO_MATCH;
906 
907   /* search if str matches the beginning of name */
908   if (str[0] && sfile->files) {
909     char dirname[FILE_MAX];
910 
911     DIR *dir;
912     struct dirent *de;
913 
914     BLI_split_dir_part(str, dirname, sizeof(dirname));
915 
916     dir = opendir(dirname);
917 
918     if (dir) {
919       AutoComplete *autocpl = UI_autocomplete_begin(str, FILE_MAX);
920 
921       while ((de = readdir(dir)) != NULL) {
922         if (FILENAME_IS_CURRPAR(de->d_name)) {
923           /* pass */
924         }
925         else {
926           char path[FILE_MAX];
927           BLI_stat_t status;
928 
929           BLI_join_dirfile(path, sizeof(path), dirname, de->d_name);
930 
931           if (BLI_stat(path, &status) == 0) {
932             if (S_ISDIR(status.st_mode)) { /* is subdir */
933               UI_autocomplete_update_name(autocpl, path);
934             }
935           }
936         }
937       }
938       closedir(dir);
939 
940       match = UI_autocomplete_end(autocpl, str);
941       if (match == AUTOCOMPLETE_FULL_MATCH) {
942         BLI_path_slash_ensure(str);
943       }
944     }
945   }
946 
947   return match;
948 }
949 
autocomplete_file(struct bContext * C,char * str,void * UNUSED (arg_v))950 int autocomplete_file(struct bContext *C, char *str, void *UNUSED(arg_v))
951 {
952   SpaceFile *sfile = CTX_wm_space_file(C);
953   int match = AUTOCOMPLETE_NO_MATCH;
954 
955   /* search if str matches the beginning of name */
956   if (str[0] && sfile->files) {
957     AutoComplete *autocpl = UI_autocomplete_begin(str, FILE_MAX);
958     int nentries = filelist_files_ensure(sfile->files);
959 
960     for (int i = 0; i < nentries; i++) {
961       FileDirEntry *file = filelist_file(sfile->files, i);
962       UI_autocomplete_update_name(autocpl, file->relpath);
963     }
964     match = UI_autocomplete_end(autocpl, str);
965   }
966 
967   return match;
968 }
969 
ED_fileselect_clear(wmWindowManager * wm,Scene * owner_scene,SpaceFile * sfile)970 void ED_fileselect_clear(wmWindowManager *wm, Scene *owner_scene, SpaceFile *sfile)
971 {
972   /* only NULL in rare cases - T29734. */
973   if (sfile->files) {
974     filelist_readjob_stop(wm, owner_scene);
975     filelist_freelib(sfile->files);
976     filelist_clear(sfile->files);
977   }
978 
979   sfile->params->highlight_file = -1;
980   WM_main_add_notifier(NC_SPACE | ND_SPACE_FILE_LIST, NULL);
981 }
982 
ED_fileselect_exit(wmWindowManager * wm,Scene * owner_scene,SpaceFile * sfile)983 void ED_fileselect_exit(wmWindowManager *wm, Scene *owner_scene, SpaceFile *sfile)
984 {
985   if (!sfile) {
986     return;
987   }
988   if (sfile->op) {
989     wmWindow *temp_win = WM_window_is_temp_screen(wm->winactive) ? wm->winactive : NULL;
990     if (temp_win) {
991       int win_size[2];
992       bool is_maximized;
993 
994       ED_fileselect_window_params_get(temp_win, win_size, &is_maximized);
995       ED_fileselect_params_to_userdef(sfile, win_size, is_maximized);
996     }
997     else {
998       ED_fileselect_params_to_userdef(sfile, NULL, false);
999     }
1000 
1001     WM_event_fileselect_event(wm, sfile->op, EVT_FILESELECT_EXTERNAL_CANCEL);
1002     sfile->op = NULL;
1003   }
1004 
1005   folderlist_free(sfile->folders_prev);
1006   folderlist_free(sfile->folders_next);
1007 
1008   if (sfile->files) {
1009     ED_fileselect_clear(wm, owner_scene, sfile);
1010     filelist_free(sfile->files);
1011     MEM_freeN(sfile->files);
1012     sfile->files = NULL;
1013   }
1014 }
1015 
1016 /**
1017  * Helper used by both main update code, and smooth-scroll timer,
1018  * to try to enable rename editing from #FileSelectParams.renamefile name.
1019  */
file_params_renamefile_activate(SpaceFile * sfile,FileSelectParams * params)1020 void file_params_renamefile_activate(SpaceFile *sfile, FileSelectParams *params)
1021 {
1022   BLI_assert(params->rename_flag != 0);
1023 
1024   if ((params->rename_flag & (FILE_PARAMS_RENAME_ACTIVE | FILE_PARAMS_RENAME_POSTSCROLL_ACTIVE)) !=
1025       0) {
1026     return;
1027   }
1028 
1029   BLI_assert(params->renamefile[0] != '\0');
1030 
1031   const int idx = filelist_file_findpath(sfile->files, params->renamefile);
1032   if (idx >= 0) {
1033     FileDirEntry *file = filelist_file(sfile->files, idx);
1034     BLI_assert(file != NULL);
1035 
1036     if ((params->rename_flag & FILE_PARAMS_RENAME_PENDING) != 0) {
1037       filelist_entry_select_set(sfile->files, file, FILE_SEL_ADD, FILE_SEL_EDITING, CHECK_ALL);
1038       params->rename_flag = FILE_PARAMS_RENAME_ACTIVE;
1039     }
1040     else if ((params->rename_flag & FILE_PARAMS_RENAME_POSTSCROLL_PENDING) != 0) {
1041       filelist_entry_select_set(sfile->files, file, FILE_SEL_ADD, FILE_SEL_HIGHLIGHTED, CHECK_ALL);
1042       params->renamefile[0] = '\0';
1043       params->rename_flag = FILE_PARAMS_RENAME_POSTSCROLL_ACTIVE;
1044     }
1045   }
1046   /* File listing is now async, only reset renaming if matching entry is not found
1047    * when file listing is not done. */
1048   else if (filelist_is_ready(sfile->files)) {
1049     params->renamefile[0] = '\0';
1050     params->rename_flag = 0;
1051   }
1052 }
1053