Cleanup: remove redundant doxygen \file argument
[blender.git] / source / blender / editors / space_file / filesel.c
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 \ingroup spfile
21  */
22
23
24 #include <string.h>
25 #include <stdio.h>
26 #include <math.h>
27
28 #include <sys/stat.h>
29 #include <sys/types.h>
30
31 /* path/file handeling stuff */
32 #ifdef WIN32
33 #  include <io.h>
34 #  include <direct.h>
35 #  include "BLI_winstuff.h"
36 #else
37 #  include <unistd.h>
38 #  include <sys/times.h>
39 #  include <dirent.h>
40 #endif
41
42 #include "DNA_space_types.h"
43 #include "DNA_screen_types.h"
44 #include "DNA_userdef_types.h"
45
46 #include "MEM_guardedalloc.h"
47
48 #include "BLI_blenlib.h"
49 #include "BLI_utildefines.h"
50 #include "BLI_fnmatch.h"
51
52 #include "BKE_appdir.h"
53 #include "BKE_context.h"
54 #include "BKE_main.h"
55
56 #include "BLF_api.h"
57
58 #include "ED_fileselect.h"
59
60 #include "WM_api.h"
61 #include "WM_types.h"
62
63 #include "RNA_access.h"
64
65 #include "UI_interface.h"
66 #include "UI_interface_icons.h"
67 #include "UI_view2d.h"
68
69 #include "file_intern.h"
70 #include "filelist.h"
71
72 FileSelectParams *ED_fileselect_get_params(struct SpaceFile *sfile)
73 {
74         if (!sfile->params) {
75                 ED_fileselect_set_params(sfile);
76         }
77         return sfile->params;
78 }
79
80 /**
81  * \note RNA_struct_property_is_set_ex is used here because we want
82  *       the previously used settings to be used here rather then overriding them */
83 short ED_fileselect_set_params(SpaceFile *sfile)
84 {
85         FileSelectParams *params;
86         wmOperator *op = sfile->op;
87
88         const char *blendfile_path = BKE_main_blendfile_path_from_global();
89
90         /* create new parameters if necessary */
91         if (!sfile->params) {
92                 sfile->params = MEM_callocN(sizeof(FileSelectParams), "fileselparams");
93                 /* set path to most recently opened .blend */
94                 BLI_split_dirfile(blendfile_path, sfile->params->dir, sfile->params->file, sizeof(sfile->params->dir), sizeof(sfile->params->file));
95                 sfile->params->filter_glob[0] = '\0';
96                 /* set the default thumbnails size */
97                 sfile->params->thumbnail_size = 128;
98         }
99
100         params = sfile->params;
101
102         /* set the parameters from the operator, if it exists */
103         if (op) {
104                 PropertyRNA *prop;
105                 const bool is_files = (RNA_struct_find_property(op->ptr, "files") != NULL);
106                 const bool is_filepath = (RNA_struct_find_property(op->ptr, "filepath") != NULL);
107                 const bool is_filename = (RNA_struct_find_property(op->ptr, "filename") != NULL);
108                 const bool is_directory = (RNA_struct_find_property(op->ptr, "directory") != NULL);
109                 const bool is_relative_path = (RNA_struct_find_property(op->ptr, "relative_path") != NULL);
110
111                 BLI_strncpy_utf8(params->title, RNA_struct_ui_name(op->type->srna), sizeof(params->title));
112
113                 if ((prop = RNA_struct_find_property(op->ptr, "filemode"))) {
114                         params->type = RNA_property_int_get(op->ptr, prop);
115                 }
116                 else {
117                         params->type = FILE_SPECIAL;
118                 }
119
120                 if (is_filepath && RNA_struct_property_is_set_ex(op->ptr, "filepath", false)) {
121                         char name[FILE_MAX];
122                         RNA_string_get(op->ptr, "filepath", name);
123                         if (params->type == FILE_LOADLIB) {
124                                 BLI_strncpy(params->dir, name, sizeof(params->dir));
125                                 sfile->params->file[0] = '\0';
126                         }
127                         else {
128                                 BLI_split_dirfile(name, sfile->params->dir, sfile->params->file, sizeof(sfile->params->dir), sizeof(sfile->params->file));
129                         }
130                 }
131                 else {
132                         if (is_directory && RNA_struct_property_is_set_ex(op->ptr, "directory", false)) {
133                                 RNA_string_get(op->ptr, "directory", params->dir);
134                                 sfile->params->file[0] = '\0';
135                         }
136
137                         if (is_filename && RNA_struct_property_is_set_ex(op->ptr, "filename", false)) {
138                                 RNA_string_get(op->ptr, "filename", params->file);
139                         }
140                 }
141
142                 if (params->dir[0]) {
143                         BLI_cleanup_dir(blendfile_path, params->dir);
144                         BLI_path_abs(params->dir, blendfile_path);
145                 }
146
147                 if (is_directory == true && is_filename == false && is_filepath == false && is_files == false) {
148                         params->flag |= FILE_DIRSEL_ONLY;
149                 }
150                 else {
151                         params->flag &= ~FILE_DIRSEL_ONLY;
152                 }
153
154                 params->filter = 0;
155                 if ((prop = RNA_struct_find_property(op->ptr, "filter_blender")))
156                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BLENDER : 0;
157                 if ((prop = RNA_struct_find_property(op->ptr, "filter_blenlib")))
158                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BLENDERLIB : 0;
159                 if ((prop = RNA_struct_find_property(op->ptr, "filter_backup")))
160                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BLENDER_BACKUP : 0;
161                 if ((prop = RNA_struct_find_property(op->ptr, "filter_image")))
162                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_IMAGE : 0;
163                 if ((prop = RNA_struct_find_property(op->ptr, "filter_movie")))
164                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_MOVIE : 0;
165                 if ((prop = RNA_struct_find_property(op->ptr, "filter_python")))
166                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_PYSCRIPT : 0;
167                 if ((prop = RNA_struct_find_property(op->ptr, "filter_font")))
168                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_FTFONT : 0;
169                 if ((prop = RNA_struct_find_property(op->ptr, "filter_sound")))
170                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_SOUND : 0;
171                 if ((prop = RNA_struct_find_property(op->ptr, "filter_text")))
172                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_TEXT : 0;
173                 if ((prop = RNA_struct_find_property(op->ptr, "filter_folder")))
174                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_FOLDER : 0;
175                 if ((prop = RNA_struct_find_property(op->ptr, "filter_btx")))
176                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_BTX : 0;
177                 if ((prop = RNA_struct_find_property(op->ptr, "filter_collada")))
178                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_COLLADA : 0;
179                 if ((prop = RNA_struct_find_property(op->ptr, "filter_alembic")))
180                         params->filter |= RNA_property_boolean_get(op->ptr, prop) ? FILE_TYPE_ALEMBIC : 0;
181                 if ((prop = RNA_struct_find_property(op->ptr, "filter_glob"))) {
182                         /* Protection against pyscripts not setting proper size limit... */
183                         char *tmp = RNA_property_string_get_alloc(
184                                         op->ptr, prop, params->filter_glob, sizeof(params->filter_glob), NULL);
185                         if (tmp != params->filter_glob) {
186                                 BLI_strncpy(params->filter_glob, tmp, sizeof(params->filter_glob));
187                                 MEM_freeN(tmp);
188
189                                 /* Fix stupid things that truncating might have generated,
190                                  * like last group being a 'match everything' wildcard-only one... */
191                                 BLI_path_extension_glob_validate(params->filter_glob);
192                         }
193                         params->filter |= (FILE_TYPE_OPERATOR | FILE_TYPE_FOLDER);
194                 }
195                 else {
196                         params->filter_glob[0] = '\0';
197                 }
198
199                 if (params->filter != 0) {
200                         if (U.uiflag & USER_FILTERFILEEXTS) {
201                                 params->flag |= FILE_FILTER;
202                         }
203                         else {
204                                 params->flag &= ~FILE_FILTER;
205                         }
206                 }
207
208                 /* For now, always init filterid to 'all true' */
209                 params->filter_id = FILTER_ID_AC | FILTER_ID_AR | FILTER_ID_BR | FILTER_ID_CA | FILTER_ID_CU | FILTER_ID_GD |
210                                     FILTER_ID_GR | FILTER_ID_IM | FILTER_ID_LA | FILTER_ID_LS | FILTER_ID_LT | FILTER_ID_MA |
211                                     FILTER_ID_MB | FILTER_ID_MC | FILTER_ID_ME | FILTER_ID_MSK | FILTER_ID_NT | FILTER_ID_OB |
212                                     FILTER_ID_PA | FILTER_ID_PAL | FILTER_ID_PC | FILTER_ID_SCE | FILTER_ID_SPK | FILTER_ID_SO |
213                                     FILTER_ID_TE | FILTER_ID_TXT | FILTER_ID_VF | FILTER_ID_WO | FILTER_ID_CF | FILTER_ID_WS |
214                                     FILTER_ID_LP;
215
216                 if (U.uiflag & USER_HIDE_DOT) {
217                         params->flag |= FILE_HIDE_DOT;
218                 }
219                 else {
220                         params->flag &= ~FILE_HIDE_DOT;
221                 }
222
223
224                 if (params->type == FILE_LOADLIB) {
225                         params->flag |= RNA_boolean_get(op->ptr, "link") ? FILE_LINK : 0;
226                         params->flag |= RNA_boolean_get(op->ptr, "autoselect") ? FILE_AUTOSELECT : 0;
227                         params->flag |= RNA_boolean_get(op->ptr, "active_collection") ? FILE_ACTIVE_COLLECTION : 0;
228                 }
229
230                 if ((prop = RNA_struct_find_property(op->ptr, "display_type"))) {
231                         params->display = RNA_property_enum_get(op->ptr, prop);
232                 }
233
234                 if ((prop = RNA_struct_find_property(op->ptr, "sort_method"))) {
235                         params->sort = RNA_property_enum_get(op->ptr, prop);
236                 }
237                 else {
238                         params->sort = FILE_SORT_ALPHA;
239                 }
240
241                 if (params->display == FILE_DEFAULTDISPLAY) {
242                         if (U.uiflag & USER_SHOW_THUMBNAILS) {
243                                 if (params->filter & (FILE_TYPE_IMAGE | FILE_TYPE_MOVIE | FILE_TYPE_FTFONT))
244                                         params->display = FILE_IMGDISPLAY;
245                                 else
246                                         params->display = FILE_SHORTDISPLAY;
247                         }
248                         else {
249                                 params->display = FILE_SHORTDISPLAY;
250                         }
251                 }
252
253                 if (is_relative_path) {
254                         if ((prop = RNA_struct_find_property(op->ptr, "relative_path"))) {
255                                 if (!RNA_property_is_set_ex(op->ptr, prop, false)) {
256                                         RNA_property_boolean_set(op->ptr, prop, (U.flag & USER_RELPATHS) != 0);
257                                 }
258                         }
259                 }
260         }
261         else {
262                 /* default values, if no operator */
263                 params->type = FILE_UNIX;
264                 params->flag |= FILE_HIDE_DOT;
265                 params->flag &= ~FILE_DIRSEL_ONLY;
266                 params->display = FILE_SHORTDISPLAY;
267                 params->sort = FILE_SORT_ALPHA;
268                 params->filter = 0;
269                 params->filter_glob[0] = '\0';
270         }
271
272         /* operator has no setting for this */
273         params->active_file = -1;
274
275
276         /* initialize the list with previous folders */
277         if (!sfile->folders_prev)
278                 sfile->folders_prev = folderlist_new();
279
280         if (!sfile->params->dir[0]) {
281                 if (blendfile_path[0] != '\0') {
282                         BLI_split_dir_part(blendfile_path, sfile->params->dir, sizeof(sfile->params->dir));
283                 }
284                 else {
285                         const char *doc_path = BKE_appdir_folder_default();
286                         if (doc_path) {
287                                 BLI_strncpy(sfile->params->dir, doc_path, sizeof(sfile->params->dir));
288                         }
289                 }
290         }
291
292         folderlist_pushdir(sfile->folders_prev, sfile->params->dir);
293
294         /* switching thumbnails needs to recalc layout [#28809] */
295         if (sfile->layout) {
296                 sfile->layout->dirty = true;
297         }
298
299         return 1;
300 }
301
302 void ED_fileselect_reset_params(SpaceFile *sfile)
303 {
304         sfile->params->type = FILE_UNIX;
305         sfile->params->flag = 0;
306         sfile->params->title[0] = '\0';
307         sfile->params->active_file = -1;
308 }
309
310 /**
311  * Sets FileSelectParams->file (name of selected file)
312  */
313 void fileselect_file_set(SpaceFile *sfile, const int index)
314 {
315         const struct FileDirEntry *file = filelist_file(sfile->files, index);
316         if (file && file->relpath && file->relpath[0] && !(file->typeflag & FILE_TYPE_FOLDER)) {
317                 BLI_strncpy(sfile->params->file, file->relpath, FILE_MAXFILE);
318         }
319 }
320
321 int ED_fileselect_layout_numfiles(FileLayout *layout, ARegion *ar)
322 {
323         int numfiles;
324
325         /* Values in pixels.
326          *
327          * - *_item: size of each (row|col), (including padding)
328          * - *_view: (x|y) size of the view.
329          * - *_over: extra pixels, to take into account, when the fit isnt exact
330          *   (needed since you may see the end of the previous column and the beginning of the next).
331          *
332          * Could be more clever and take scrolling into account,
333          * but for now don't bother.
334          */
335         if (layout->flag & FILE_LAYOUT_HOR) {
336                 const int x_item = layout->tile_w + (2 * layout->tile_border_x);
337                 const int x_view = (int)(BLI_rctf_size_x(&ar->v2d.cur));
338                 const int x_over = x_item - (x_view % x_item);
339                 numfiles = (int)((float)(x_view + x_over) / (float)(x_item));
340                 return numfiles * layout->rows;
341         }
342         else {
343                 const int y_item = layout->tile_h + (2 * layout->tile_border_y);
344                 const int y_view = (int)(BLI_rctf_size_y(&ar->v2d.cur));
345                 const int y_over = y_item - (y_view % y_item);
346                 numfiles = (int)((float)(y_view + y_over) / (float)(y_item));
347                 return numfiles * layout->columns;
348         }
349 }
350
351 static bool is_inside(int x, int y, int cols, int rows)
352 {
353         return ((x >= 0) && (x < cols) && (y >= 0) && (y < rows));
354 }
355
356 FileSelection ED_fileselect_layout_offset_rect(FileLayout *layout, const rcti *rect)
357 {
358         int colmin, colmax, rowmin, rowmax;
359         FileSelection sel;
360         sel.first = sel.last = -1;
361
362         if (layout == NULL)
363                 return sel;
364
365         colmin = (rect->xmin) / (layout->tile_w + 2 * layout->tile_border_x);
366         rowmin = (rect->ymin) / (layout->tile_h + 2 * layout->tile_border_y);
367         colmax = (rect->xmax) / (layout->tile_w + 2 * layout->tile_border_x);
368         rowmax = (rect->ymax) / (layout->tile_h + 2 * layout->tile_border_y);
369
370         if (is_inside(colmin, rowmin, layout->columns, layout->rows) ||
371             is_inside(colmax, rowmax, layout->columns, layout->rows) )
372         {
373                 CLAMP(colmin, 0, layout->columns - 1);
374                 CLAMP(rowmin, 0, layout->rows - 1);
375                 CLAMP(colmax, 0, layout->columns - 1);
376                 CLAMP(rowmax, 0, layout->rows - 1);
377         }
378
379         if ((colmin > layout->columns - 1) || (rowmin > layout->rows - 1)) {
380                 sel.first = -1;
381         }
382         else {
383                 if (layout->flag & FILE_LAYOUT_HOR)
384                         sel.first = layout->rows * colmin + rowmin;
385                 else
386                         sel.first = colmin + layout->columns * rowmin;
387         }
388         if ((colmax > layout->columns - 1) || (rowmax > layout->rows - 1)) {
389                 sel.last = -1;
390         }
391         else {
392                 if (layout->flag & FILE_LAYOUT_HOR)
393                         sel.last = layout->rows * colmax + rowmax;
394                 else
395                         sel.last = colmax + layout->columns * rowmax;
396         }
397
398         return sel;
399 }
400
401 int ED_fileselect_layout_offset(FileLayout *layout, int x, int y)
402 {
403         int offsetx, offsety;
404         int active_file;
405
406         if (layout == NULL)
407                 return -1;
408
409         offsetx = (x) / (layout->tile_w + 2 * layout->tile_border_x);
410         offsety = (y) / (layout->tile_h + 2 * layout->tile_border_y);
411
412         if (offsetx > layout->columns - 1) return -1;
413         if (offsety > layout->rows - 1) return -1;
414
415         if (layout->flag & FILE_LAYOUT_HOR)
416                 active_file = layout->rows * offsetx + offsety;
417         else
418                 active_file = offsetx + layout->columns * offsety;
419         return active_file;
420 }
421
422 void ED_fileselect_layout_tilepos(FileLayout *layout, int tile, int *x, int *y)
423 {
424         if (layout->flag == FILE_LAYOUT_HOR) {
425                 *x = layout->tile_border_x + (tile / layout->rows) * (layout->tile_w + 2 * layout->tile_border_x);
426                 *y = layout->tile_border_y + (tile % layout->rows) * (layout->tile_h + 2 * layout->tile_border_y);
427         }
428         else {
429                 *x = layout->tile_border_x + ((tile) % layout->columns) * (layout->tile_w + 2 * layout->tile_border_x);
430                 *y = layout->tile_border_y + ((tile) / layout->columns) * (layout->tile_h + 2 * layout->tile_border_y);
431         }
432 }
433
434 float file_string_width(const char *str)
435 {
436         uiStyle *style = UI_style_get();
437         float width;
438
439         UI_fontstyle_set(&style->widget);
440         if (style->widget.kerning == 1) {  /* for BLF_width */
441                 BLF_enable(style->widget.uifont_id, BLF_KERNING_DEFAULT);
442         }
443
444         width = BLF_width(style->widget.uifont_id, str, BLF_DRAW_STR_DUMMY_MAX);
445
446         if (style->widget.kerning == 1) {
447                 BLF_disable(style->widget.uifont_id, BLF_KERNING_DEFAULT);
448         }
449
450         return width;
451 }
452
453 float file_font_pointsize(void)
454 {
455 #if 0
456         float s;
457         char tmp[2] = "X";
458         uiStyle *style = UI_style_get();
459         UI_fontstyle_set(&style->widget);
460         s = BLF_height(style->widget.uifont_id, tmp);
461         return style->widget.points;
462 #else
463         uiStyle *style = UI_style_get();
464         UI_fontstyle_set(&style->widget);
465         return style->widget.points * UI_DPI_FAC;
466 #endif
467 }
468
469 static void column_widths(FileSelectParams *params, struct FileLayout *layout)
470 {
471         int i;
472         const bool small_size = SMALL_SIZE_CHECK(params->thumbnail_size);
473
474         for (i = 0; i < MAX_FILE_COLUMN; ++i) {
475                 layout->column_widths[i] = 0;
476         }
477
478         layout->column_widths[COLUMN_NAME] = ((float)params->thumbnail_size / 8.0f) * UI_UNIT_X;
479         /* Biggest possible reasonable values... */
480         layout->column_widths[COLUMN_DATE] = file_string_width(small_size ? "23/08/89" : "23-Dec-89");
481         layout->column_widths[COLUMN_TIME] = file_string_width("23:59");
482         layout->column_widths[COLUMN_SIZE] = file_string_width(small_size ? "98.7 M" : "98.7 MiB");
483 }
484
485 void ED_fileselect_init_layout(struct SpaceFile *sfile, ARegion *ar)
486 {
487         FileSelectParams *params = ED_fileselect_get_params(sfile);
488         FileLayout *layout = NULL;
489         View2D *v2d = &ar->v2d;
490         int maxlen = 0;
491         int numfiles;
492         int textheight;
493
494         if (sfile->layout == NULL) {
495                 sfile->layout = MEM_callocN(sizeof(struct FileLayout), "file_layout");
496                 sfile->layout->dirty = true;
497         }
498         else if (sfile->layout->dirty == false) {
499                 return;
500         }
501
502         numfiles = filelist_files_ensure(sfile->files);
503         textheight = (int)file_font_pointsize();
504         layout = sfile->layout;
505         layout->textheight = textheight;
506
507         if (params->display == FILE_IMGDISPLAY) {
508                 layout->prv_w = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_X;
509                 layout->prv_h = ((float)params->thumbnail_size / 20.0f) * UI_UNIT_Y;
510                 layout->tile_border_x = 0.3f * UI_UNIT_X;
511                 layout->tile_border_y = 0.3f * UI_UNIT_X;
512                 layout->prv_border_x = 0.3f * UI_UNIT_X;
513                 layout->prv_border_y = 0.3f * UI_UNIT_Y;
514                 layout->tile_w = layout->prv_w + 2 * layout->prv_border_x;
515                 layout->tile_h = layout->prv_h + 2 * layout->prv_border_y + textheight;
516                 layout->width = (int)(BLI_rctf_size_x(&v2d->cur) - 2 * layout->tile_border_x);
517                 layout->columns = layout->width / (layout->tile_w + 2 * layout->tile_border_x);
518                 if (layout->columns > 0)
519                         layout->rows = numfiles / layout->columns + 1;  // XXX dirty, modulo is zero
520                 else {
521                         layout->columns = 1;
522                         layout->rows = numfiles + 1; // XXX dirty, modulo is zero
523                 }
524                 layout->height = sfile->layout->rows * (layout->tile_h + 2 * layout->tile_border_y) + layout->tile_border_y * 2;
525                 layout->flag = FILE_LAYOUT_VER;
526         }
527         else {
528                 int column_space = 0.6f * UI_UNIT_X;
529                 int column_icon_space = 0.2f * UI_UNIT_X;
530
531                 layout->prv_w = 0;
532                 layout->prv_h = 0;
533                 layout->tile_border_x = 0.4f * UI_UNIT_X;
534                 layout->tile_border_y = 0.1f * UI_UNIT_Y;
535                 layout->prv_border_x = 0;
536                 layout->prv_border_y = 0;
537                 layout->tile_h = textheight * 3 / 2;
538                 layout->height = (int)(BLI_rctf_size_y(&v2d->cur) - 2 * layout->tile_border_y);
539                 /* Padding by full scrollbar H is too much, can overlap tile border Y. */
540                 layout->rows = (layout->height - V2D_SCROLL_HEIGHT + layout->tile_border_y) /
541                                    (layout->tile_h + 2 * layout->tile_border_y);
542
543                 column_widths(params, layout);
544
545                 if (params->display == FILE_SHORTDISPLAY) {
546                         maxlen = ICON_DEFAULT_WIDTH_SCALE + column_icon_space +
547                                  (int)layout->column_widths[COLUMN_NAME] + column_space +
548                                  (int)layout->column_widths[COLUMN_SIZE] + column_space;
549                 }
550                 else {
551                         maxlen = ICON_DEFAULT_WIDTH_SCALE + column_icon_space +
552                                  (int)layout->column_widths[COLUMN_NAME] + column_space +
553                                  (int)layout->column_widths[COLUMN_DATE] + column_space +
554                                  (int)layout->column_widths[COLUMN_TIME] + column_space +
555                                  (int)layout->column_widths[COLUMN_SIZE] + column_space;
556
557                 }
558                 layout->tile_w = maxlen;
559                 if (layout->rows > 0)
560                         layout->columns = numfiles / layout->rows + 1;  // XXX dirty, modulo is zero
561                 else {
562                         layout->rows = 1;
563                         layout->columns = numfiles + 1; // XXX dirty, modulo is zero
564                 }
565                 layout->width = sfile->layout->columns * (layout->tile_w + 2 * layout->tile_border_x) + layout->tile_border_x * 2;
566                 layout->flag = FILE_LAYOUT_HOR;
567         }
568         layout->dirty = false;
569 }
570
571 FileLayout *ED_fileselect_get_layout(struct SpaceFile *sfile, ARegion *ar)
572 {
573         if (!sfile->layout) {
574                 ED_fileselect_init_layout(sfile, ar);
575         }
576         return sfile->layout;
577 }
578
579 void ED_file_change_dir(bContext *C)
580 {
581         wmWindowManager *wm = CTX_wm_manager(C);
582         SpaceFile *sfile = CTX_wm_space_file(C);
583         ScrArea *sa = CTX_wm_area(C);
584
585         if (sfile->params) {
586                 ED_fileselect_clear(wm, sa, sfile);
587
588                 /* Clear search string, it is very rare to want to keep that filter while changing dir,
589                  * and usually very annoying to keep it actually! */
590                 sfile->params->filter_search[0] = '\0';
591                 sfile->params->active_file = -1;
592
593                 if (!filelist_is_dir(sfile->files, sfile->params->dir)) {
594                         BLI_strncpy(sfile->params->dir, filelist_dir(sfile->files), sizeof(sfile->params->dir));
595                         /* could return but just refresh the current dir */
596                 }
597                 filelist_setdir(sfile->files, sfile->params->dir);
598
599                 if (folderlist_clear_next(sfile))
600                         folderlist_free(sfile->folders_next);
601
602                 folderlist_pushdir(sfile->folders_prev, sfile->params->dir);
603
604                 file_draw_check(C);
605         }
606 }
607
608 int file_select_match(struct SpaceFile *sfile, const char *pattern, char *matched_file)
609 {
610         int match = 0;
611
612         int i;
613         FileDirEntry *file;
614         int n = filelist_files_ensure(sfile->files);
615
616         /* select any file that matches the pattern, this includes exact match
617          * if the user selects a single file by entering the filename
618          */
619         for (i = 0; i < n; i++) {
620                 file = filelist_file(sfile->files, i);
621                 /* Do not check whether file is a file or dir here! Causes T44243
622                  * (we do accept dirs at this stage). */
623                 if (fnmatch(pattern, file->relpath, 0) == 0) {
624                         filelist_entry_select_set(sfile->files, file, FILE_SEL_ADD, FILE_SEL_SELECTED, CHECK_ALL);
625                         if (!match) {
626                                 BLI_strncpy(matched_file, file->relpath, FILE_MAX);
627                         }
628                         match++;
629                 }
630         }
631
632         return match;
633 }
634
635 int autocomplete_directory(struct bContext *C, char *str, void *UNUSED(arg_v))
636 {
637         SpaceFile *sfile = CTX_wm_space_file(C);
638         int match = AUTOCOMPLETE_NO_MATCH;
639
640         /* search if str matches the beginning of name */
641         if (str[0] && sfile->files) {
642                 char dirname[FILE_MAX];
643
644                 DIR *dir;
645                 struct dirent *de;
646
647                 BLI_split_dir_part(str, dirname, sizeof(dirname));
648
649                 dir = opendir(dirname);
650
651                 if (dir) {
652                         AutoComplete *autocpl = UI_autocomplete_begin(str, FILE_MAX);
653
654                         while ((de = readdir(dir)) != NULL) {
655                                 if (FILENAME_IS_CURRPAR(de->d_name)) {
656                                         /* pass */
657                                 }
658                                 else {
659                                         char path[FILE_MAX];
660                                         BLI_stat_t status;
661
662                                         BLI_join_dirfile(path, sizeof(path), dirname, de->d_name);
663
664                                         if (BLI_stat(path, &status) == 0) {
665                                                 if (S_ISDIR(status.st_mode)) { /* is subdir */
666                                                         UI_autocomplete_update_name(autocpl, path);
667                                                 }
668                                         }
669                                 }
670                         }
671                         closedir(dir);
672
673                         match = UI_autocomplete_end(autocpl, str);
674                         if (match == AUTOCOMPLETE_FULL_MATCH) {
675                                 BLI_add_slash(str);
676                         }
677                 }
678         }
679
680         return match;
681 }
682
683 int autocomplete_file(struct bContext *C, char *str, void *UNUSED(arg_v))
684 {
685         SpaceFile *sfile = CTX_wm_space_file(C);
686         int match = AUTOCOMPLETE_NO_MATCH;
687
688         /* search if str matches the beginning of name */
689         if (str[0] && sfile->files) {
690                 AutoComplete *autocpl = UI_autocomplete_begin(str, FILE_MAX);
691                 int nentries = filelist_files_ensure(sfile->files);
692                 int i;
693
694                 for (i = 0; i < nentries; ++i) {
695                         FileDirEntry *file = filelist_file(sfile->files, i);
696                         UI_autocomplete_update_name(autocpl, file->relpath);
697                 }
698                 match = UI_autocomplete_end(autocpl, str);
699         }
700
701         return match;
702 }
703
704 void ED_fileselect_clear(wmWindowManager *wm, ScrArea *sa, SpaceFile *sfile)
705 {
706         /* only NULL in rare cases - [#29734] */
707         if (sfile->files) {
708                 filelist_readjob_stop(wm, sa);
709                 filelist_freelib(sfile->files);
710                 filelist_clear(sfile->files);
711         }
712
713         sfile->params->highlight_file = -1;
714         WM_main_add_notifier(NC_SPACE | ND_SPACE_FILE_LIST, NULL);
715 }
716
717 void ED_fileselect_exit(wmWindowManager *wm, ScrArea *sa, SpaceFile *sfile)
718 {
719         if (!sfile) return;
720         if (sfile->op) {
721                 WM_event_fileselect_event(wm, sfile->op, EVT_FILESELECT_EXTERNAL_CANCEL);
722                 sfile->op = NULL;
723         }
724
725         folderlist_free(sfile->folders_prev);
726         folderlist_free(sfile->folders_next);
727
728         if (sfile->files) {
729                 ED_fileselect_clear(wm, sa, sfile);
730                 filelist_free(sfile->files);
731                 MEM_freeN(sfile->files);
732                 sfile->files = NULL;
733         }
734
735 }