Browse Source

Merge pull request #306 from PavelSindler/sort_files

Sorting files
PavelSindler 7 years ago
parent
commit
5e018bc051

+ 2 - 1
Firmware/Configuration.h

@@ -9,7 +9,7 @@
 
 // Firmware version
 #define FW_version "3.1.1-RC1"
-#define FW_build   128
+#define FW_build   129
 //#define FW_build   --BUILD-NUMBER--
 #define FW_version_build FW_version " b" STR(FW_build)
 
@@ -81,6 +81,7 @@
 #define EEPROM_POWER_COUNT       (EEPROM_UVLO_MESH_BED_LEVELING-17)
 #define EEPROM_DIR_DEPTH        (EEPROM_POWER_COUNT-1)
 #define EEPROM_DIRS  (EEPROM_DIR_DEPTH-80) //8 chars for each dir name, max 10 levels
+#define EEPROM_SD_SORT (EEPROM_DIRS - 1) //0 -time, 1-alpha, 2-none
 
 //TMC2130 configuration
 #define EEPROM_TMC_AXIS_SIZE  //axis configuration block size

+ 46 - 0
Firmware/Configuration_adv.h

@@ -224,6 +224,52 @@
 // using:
 //#define MENU_ADDAUTOSTART
 
+/**
+* Sort SD file listings in alphabetical order.
+*
+* With this option enabled, items on SD cards will be sorted
+* by name for easier navigation.
+*
+* By default...
+*
+*  - Use the slowest -but safest- method for sorting.
+*  - Folders are sorted to the top.
+*  - The sort key is statically allocated.
+*  - No added G-code (M34) support.
+*  - 40 item sorting limit. (Items after the first 40 are unsorted.)
+*
+* SD sorting uses static allocation (as set by SDSORT_LIMIT), allowing the
+* compiler to calculate the worst-case usage and throw an error if the SRAM
+* limit is exceeded.
+*
+*  - SDSORT_USES_RAM provides faster sorting via a static directory buffer.
+*  - SDSORT_USES_STACK does the same, but uses a local stack-based buffer.
+*  - SDSORT_CACHE_NAMES will retain the sorted file listing in RAM. (Expensive!)
+*  - SDSORT_DYNAMIC_RAM only uses RAM when the SD menu is visible. (Use with caution!)
+*/
+	#define SDCARD_SORT_ALPHA //Alphabetical sorting of SD files menu
+	
+	// SD Card Sorting options
+	// In current firmware Prusa Firmware version,
+	// SDSORT_CACHE_NAMES and SDSORT_DYNAMIC_RAM is not supported and must be set to false.
+	#ifdef SDCARD_SORT_ALPHA
+	  #define SD_SORT_TIME 0
+	  #define SD_SORT_ALPHA 1
+	  #define SD_SORT_NONE 2
+	
+	  #define SDSORT_LIMIT       100    // Maximum number of sorted items (10-256).
+	  #define FOLDER_SORTING     -1     // -1=above  0=none  1=below
+	  #define SDSORT_GCODE       false  // Allow turning sorting on/off with LCD and M34 g-code.
+	  #define SDSORT_USES_RAM    false  // Pre-allocate a static array for faster pre-sorting.
+	  #define SDSORT_USES_STACK  false  // Prefer the stack for pre-sorting to give back some SRAM. (Negated by next 2 options.)
+	  #define SDSORT_CACHE_NAMES false  // Keep sorted items in RAM longer for speedy performance. Most expensive option.
+	  #define SDSORT_DYNAMIC_RAM false  // Use dynamic allocation (within SD menus). Least expensive option. Set SDSORT_LIMIT before use!
+	#endif
+	
+	#if defined(SDCARD_SORT_ALPHA)
+	  #define HAS_FOLDER_SORTING (FOLDER_SORTING || SDSORT_GCODE)
+	#endif
+
 // Show a progress bar on the LCD when printing from SD?
 //#define LCD_PROGRESS_BAR
 

+ 1 - 0
Firmware/Marlin.h

@@ -349,6 +349,7 @@ extern bool mesh_bed_leveling_flag;
 extern bool mesh_bed_run_from_menu;
 
 extern float distance_from_min[2];
+extern bool sortAlpha;
 
 extern char dir_names[3][9];
 

+ 5 - 0
Firmware/Marlin_main.cpp

@@ -322,6 +322,8 @@ int fan_speed[2];
 
 char dir_names[3][9];
 
+bool sortAlpha = false;
+
 bool volumetric_enabled = false;
 float filament_size[EXTRUDERS] = { DEFAULT_NOMINAL_FILAMENT_DIA
   #if EXTRUDERS > 1
@@ -1098,6 +1100,9 @@ void setup()
 	if (eeprom_read_byte((uint8_t*)EEPROM_UVLO) == 255) {
 		eeprom_write_byte((uint8_t*)EEPROM_UVLO, 0);
 	}
+	if (eeprom_read_byte((uint8_t*)EEPROM_SD_SORT) == 255) {
+		eeprom_write_byte((uint8_t*)EEPROM_SD_SORT, 0);
+	}
 
 	check_babystep(); //checking if Z babystep is in allowed range
 	setup_uvlo_interrupt();

+ 418 - 85
Firmware/cardreader.cpp

@@ -7,10 +7,20 @@
 
 #ifdef SDSUPPORT
 
-
+#define LONGEST_FILENAME (longFilename[0] ? longFilename : filename)
 
 CardReader::CardReader()
 {
+
+   #ifdef SDCARD_SORT_ALPHA
+     sort_count = 0;
+     #if SDSORT_GCODE
+       sort_alpha = true;
+     sort_folders = FOLDER_SORTING;
+     //sort_reverse = false;
+     #endif
+   #endif
+
    filesize = 0;
    sdpos = 0;
    sdprinting = false;
@@ -49,95 +59,102 @@ char *createFilename(char *buffer,const dir_t &p) //buffer>12characters
   return buffer;
 }
 
-
-void CardReader::lsDive(const char *prepend, SdFile parent, const char * const match/*=NULL*/)
-{
-  dir_t p;
- uint8_t cnt=0;
- 
-  while (parent.readDir(p, longFilename) > 0)
-  {
-    if( DIR_IS_SUBDIR(&p) && lsAction!=LS_Count && lsAction!=LS_GetFilename) // hence LS_SerialPrint
-    {
-
-      char path[13*2];
-      char lfilename[13];
-      createFilename(lfilename,p);
-      
-      path[0]=0;
-      if(strlen(prepend)==0) //avoid leading / if already in prepend
-      {
-       strcat(path,"/");
-      }
-      strcat(path,prepend);
-      strcat(path,lfilename);
-      strcat(path,"/");
-      
-      //Serial.print(path);
-      
-      SdFile dir;
-      if(!dir.open(parent,lfilename, O_READ))
-      {
-        if(lsAction==LS_SerialPrint)
-        {
-          SERIAL_ECHO_START;
-          SERIAL_ECHORPGM(MSG_SD_CANT_ENTER_SUBDIR);
-          SERIAL_ECHOLN(lfilename);
-        }
-      }
-      lsDive(path,dir);
-      //close done automatically by destructor of SdFile
-
-      
-    }
-    else
-    {
-      char pn0 = p.name[0];
-      if (pn0 == DIR_NAME_FREE) break;
-      if (pn0 == DIR_NAME_DELETED || pn0 == '.' || pn0 == '_') continue;
-      char lf0 = longFilename[0];
-      if (lf0 == '.' || lf0 == '_') continue;
-
-      if (!DIR_IS_FILE_OR_SUBDIR(&p)) continue;
-      // Ignore the files and directories with hidden or system attribute.
-      if ((p.attributes & (DIR_ATT_HIDDEN | DIR_ATT_SYSTEM)) != 0) continue;
-      filenameIsDir=DIR_IS_SUBDIR(&p);
-      
-      
-      if(!filenameIsDir)
-      {
-        if(p.name[8]!='G') continue;
-        if(p.name[9]=='~') continue;
-      }
-      //if(cnt++!=nr) continue;
-      createFilename(filename,p);
-      if(lsAction==LS_SerialPrint)
-      {
-        SERIAL_PROTOCOL(prepend);
-        SERIAL_PROTOCOLLN(filename);
-      }
-      else if(lsAction==LS_Count)
-      {
-        nrFiles++;
-      } 
-      else if(lsAction==LS_GetFilename)
-      {
-        if (match != NULL) {
-          if (strcasecmp(match, filename) == 0) return;
-        }
-        else if (cnt == nrFiles) return;
-        cnt++;
-        
-      }
-    }
-  }
+/**
++* Dive into a folder and recurse depth-first to perform a pre-set operation lsAction:
++*   LS_Count       - Add +1 to nrFiles for every file within the parent
++*   LS_GetFilename - Get the filename of the file indexed by nrFiles
++*   LS_SerialPrint - Print the full path and size of each file to serial output
++*/
+
+void CardReader::lsDive(const char *prepend, SdFile parent, const char * const match/*=NULL*/) {
+	dir_t p;
+	uint8_t cnt = 0;
+		// Read the next entry from a directory
+		while (parent.readDir(p, longFilename) > 0) {
+			// If the entry is a directory and the action is LS_SerialPrint
+			if (DIR_IS_SUBDIR(&p) && lsAction != LS_Count && lsAction != LS_GetFilename) {
+				// Get the short name for the item, which we know is a folder
+				char lfilename[FILENAME_LENGTH];
+				createFilename(lfilename, p);
+				// Allocate enough stack space for the full path to a folder, trailing slash, and nul
+				bool prepend_is_empty = (prepend[0] == '\0');
+				int len = (prepend_is_empty ? 1 : strlen(prepend)) + strlen(lfilename) + 1 + 1;
+				char path[len];
+				// Append the FOLDERNAME12/ to the passed string.
+				// It contains the full path to the "parent" argument.
+				// We now have the full path to the item in this folder.
+				strcpy(path, prepend_is_empty ? "/" : prepend); // root slash if prepend is empty
+				strcat(path, lfilename); // FILENAME_LENGTH-1 characters maximum
+				strcat(path, "/");       // 1 character
+				// Serial.print(path);
+				// Get a new directory object using the full path
+				// and dive recursively into it.
+				SdFile dir;
+				if (!dir.open(parent, lfilename, O_READ)) {
+					if (lsAction == LS_SerialPrint) {
+						//SERIAL_ECHO_START();
+						//SERIAL_ECHOPGM(MSG_SD_CANT_OPEN_SUBDIR);
+						//SERIAL_ECHOLN(lfilename);
+					}
+				}
+				lsDive(path, dir);
+				// close() is done automatically by destructor of SdFile
+			}
+			else {
+				uint8_t pn0 = p.name[0];
+				if (pn0 == DIR_NAME_FREE) break;
+				if (pn0 == DIR_NAME_DELETED || pn0 == '.') continue;
+				if (longFilename[0] == '.') continue;
+				if (!DIR_IS_FILE_OR_SUBDIR(&p) || (p.attributes & DIR_ATT_HIDDEN)) continue;
+				filenameIsDir = DIR_IS_SUBDIR(&p);
+				if (!filenameIsDir && (p.name[8] != 'G' || p.name[9] == '~')) continue;
+				switch (lsAction) {
+					case LS_Count:
+						nrFiles++;
+						break;
+					
+					case LS_SerialPrint:
+						createFilename(filename, p);
+						SERIAL_PROTOCOL(prepend);
+						SERIAL_PROTOCOL(filename);
+						MYSERIAL.write(' ');
+						SERIAL_PROTOCOLLN(p.fileSize);
+						break;
+				
+					case LS_GetFilename:
+						//SERIAL_ECHOPGM("File: ");				
+						createFilename(filename, p);
+						cluster = parent.curCluster();
+						position = parent.curPosition();
+						/*MYSERIAL.println(filename);
+						SERIAL_ECHOPGM("Write date: ");
+						writeDate = p.lastWriteDate;
+						MYSERIAL.println(writeDate);
+						writeTime = p.lastWriteTime;
+						SERIAL_ECHOPGM("Creation date: ");
+						MYSERIAL.println(p.creationDate);
+						SERIAL_ECHOPGM("Access date: ");
+						MYSERIAL.println(p.lastAccessDate);
+						SERIAL_ECHOLNPGM("");*/
+						creationDate = p.creationDate;
+						creationTime = p.creationTime;
+						//writeDate = p.lastAccessDate;
+						if (match != NULL) {
+							if (strcasecmp(match, filename) == 0) return;
+						}
+						else if (cnt == nrFiles) return;
+						cnt++;
+						break;
+				}
+			}
+		} // while readDir
 }
 
 void CardReader::ls() 
 {
   lsAction=LS_SerialPrint;
-  if(lsAction==LS_Count)
-  nrFiles=0;
+  //if(lsAction==LS_Count)
+  //nrFiles=0;
 
   root.rewind();
   lsDive("",root);
@@ -185,6 +202,11 @@ void CardReader::initsd()
   }
   workDir=root;
   curDir=&root;
+
+  #ifdef SDCARD_SORT_ALPHA
+	presort();
+  #endif
+
   /*
   if(!workDir.openRoot(&volume))
   {
@@ -203,6 +225,9 @@ void CardReader::setroot()
   workDir=root;
   
   curDir=&workDir;
+  #ifdef SDCARD_SORT_ALPHA
+	  presort();
+  #endif
 }
 void CardReader::release()
 {
@@ -215,6 +240,9 @@ void CardReader::startFileprint()
   if(cardOK)
   {
     sdprinting = true;
+	#ifdef SDCARD_SORT_ALPHA
+		//flush_presort();
+	#endif
   }
 }
 
@@ -458,6 +486,9 @@ void CardReader::removeFile(char* name)
       SERIAL_PROTOCOLPGM("File deleted:");
       SERIAL_PROTOCOLLN(fname);
       sdpos = 0;
+	  #ifdef SDCARD_SORT_ALPHA
+		  presort();
+	  #endif
     }
     else
     {
@@ -607,6 +638,15 @@ void CardReader::getfilename(uint16_t nr, const char * const match/*=NULL*/)
   
 }
 
+void CardReader::getfilename_simple(uint32_t position, const char * const match/*=NULL*/)
+{
+	curDir = &workDir;
+	lsAction = LS_GetFilename;
+	nrFiles = 0;
+	curDir->seekSet(position);
+	lsDive("", *curDir, match);
+}
+
 uint16_t CardReader::getnrfilenames()
 {
   curDir=&workDir;
@@ -640,6 +680,9 @@ void CardReader::chdir(const char * relpath)
       workDirParents[0]=*parent;
     }
     workDir=newfile;
+	#ifdef SDCARD_SORT_ALPHA
+		presort();
+	#endif
   }
 }
 
@@ -652,9 +695,296 @@ void CardReader::updir()
 	int d;
     for (int d = 0; d < workDirDepth; d++)
       workDirParents[d] = workDirParents[d+1];
+	#ifdef SDCARD_SORT_ALPHA
+		presort();
+	#endif
   }
 }
 
+#ifdef SDCARD_SORT_ALPHA
+
+/**
+* Get the name of a file in the current directory by sort-index
+*/
+void CardReader::getfilename_sorted(const uint16_t nr) {
+	getfilename(
+	#if SDSORT_GCODE
+		sort_alpha &&
+	#endif
+		(nr < sort_count) ? sort_order[nr] : nr
+	);
+}
+
+/**
+* Read all the files and produce a sort key
+*
+* We can do this in 3 ways...
+*  - Minimal RAM: Read two filenames at a time sorting along...
+*  - Some RAM: Buffer the directory just for this sort
+*  - Most RAM: Buffer the directory and return filenames from RAM
+*/
+void CardReader::presort() {
+	if (farm_mode || IS_SD_INSERTED == false) return; //sorting is not used in farm mode
+	uint8_t sdSort = eeprom_read_byte((uint8_t*)EEPROM_SD_SORT);
+
+	if (sdSort == SD_SORT_NONE) return; //sd sort is turned off
+
+	#if SDSORT_GCODE
+	if (!sort_alpha) return;
+	#endif
+	KEEPALIVE_STATE(IN_HANDLER);
+
+	// Throw away old sort index
+	flush_presort();
+
+	// If there are files, sort up to the limit
+	uint16_t fileCnt = getnrfilenames();
+	if (fileCnt > 0) {
+
+		// Never sort more than the max allowed
+		// If you use folders to organize, 20 may be enough
+		if (fileCnt > SDSORT_LIMIT) {
+			lcd_show_fullscreen_message_and_wait_P(MSG_FILE_CNT);
+			fileCnt = SDSORT_LIMIT;
+		}
+		lcd_implementation_clear();
+		#if !SDSORT_USES_RAM
+			lcd_set_progress();
+		#endif
+		lcd_print_at_PGM(0, 1, MSG_SORTING);
+
+		// Sort order is always needed. May be static or dynamic.
+		#if SDSORT_DYNAMIC_RAM
+		sort_order = new uint8_t[fileCnt];
+		#endif
+
+		// Use RAM to store the entire directory during pre-sort.
+		// SDSORT_LIMIT should be set to prevent over-allocation.
+		#if SDSORT_USES_RAM
+
+		// If using dynamic ram for names, allocate on the heap.
+		#if SDSORT_CACHE_NAMES
+		#if SDSORT_DYNAMIC_RAM
+		sortshort = new char*[fileCnt];
+		sortnames = new char*[fileCnt];
+		#endif
+		#elif SDSORT_USES_STACK
+		char sortnames[fileCnt][LONG_FILENAME_LENGTH];
+		uint16_t creation_time[fileCnt];
+		uint16_t creation_date[fileCnt];
+		#endif
+
+		// Folder sorting needs 1 bit per entry for flags.
+		#if HAS_FOLDER_SORTING
+		#if SDSORT_DYNAMIC_RAM
+		isDir = new uint8_t[(fileCnt + 7) >> 3];
+		#elif SDSORT_USES_STACK
+		uint8_t isDir[(fileCnt + 7) >> 3];
+		#endif
+		#endif
+
+		#else // !SDSORT_USES_RAM
+
+		uint32_t positions[fileCnt];
+
+		// By default re-read the names from SD for every compare
+		// retaining only two filenames at a time. This is very
+		// slow but is safest and uses minimal RAM.
+		char name1[LONG_FILENAME_LENGTH + 1];
+		uint16_t creation_time_bckp;
+		uint16_t creation_date_bckp;
+
+		#endif
+		position = 0;
+		if (fileCnt > 1) {
+			// Init sort order.
+			for (uint16_t i = 0; i < fileCnt; i++) {
+				if (!IS_SD_INSERTED) return;
+				manage_heater();
+				sort_order[i] = i;
+				positions[i] = position;
+				getfilename(i);
+				// If using RAM then read all filenames now.
+				#if SDSORT_USES_RAM
+				getfilename(i);
+				#if SDSORT_DYNAMIC_RAM
+				// Use dynamic method to copy long filename
+				sortnames[i] = strdup(LONGEST_FILENAME);
+				#if SDSORT_CACHE_NAMES
+				// When caching also store the short name, since
+				// we're replacing the getfilename() behavior.
+				sortshort[i] = strdup(filename);
+				#endif
+				#else
+				// Copy filenames into the static array
+				strcpy(sortnames[i], LONGEST_FILENAME);
+				creation_time[i] = creationTime;
+				creation_date[i] = creationDate;
+				#if SDSORT_CACHE_NAMES
+				strcpy(sortshort[i], filename);
+				#endif
+				#endif
+				// char out[30];
+				// sprintf_P(out, PSTR("---- %i %s %s"), i, filenameIsDir ? "D" : " ", sortnames[i]);
+				// SERIAL_ECHOLN(out);
+				#if HAS_FOLDER_SORTING
+				const uint16_t bit = i & 0x07, ind = i >> 3;
+				if (bit == 0) isDir[ind] = 0x00;
+				if (filenameIsDir) isDir[ind] |= _BV(bit);
+				#endif
+				#endif
+			}
+
+#ifdef QUICKSORT
+			quicksort(0, fileCnt - 1);
+#else //Qicksort not defined, use Bubble Sort
+			uint32_t counter = 0;
+			uint16_t total = 0.5*(fileCnt - 1)*(fileCnt);
+
+			// Compare names from the array or just the two buffered names
+			#if SDSORT_USES_RAM
+			#define _SORT_CMP_NODIR() (strcasecmp(sortnames[o1], sortnames[o2]) > 0)
+			#define _SORT_CMP_TIME_NODIR() (((creation_date[o1] == creation_date[o2]) && (creation_time[o1] < creation_time[o2])) || \
+																	(creation_date[o1] < creation_date [o2]))
+			#else
+			#define _SORT_CMP_NODIR() (strcasecmp(name1, name2) > 0) //true if lowercase(name1) > lowercase(name2)
+			#define _SORT_CMP_TIME_NODIR() (((creation_date_bckp == creationDate) && (creation_time_bckp > creationTime)) || \
+																	(creation_date_bckp > creationDate))
+
+			#endif
+
+			#if HAS_FOLDER_SORTING
+			#if SDSORT_USES_RAM
+			// Folder sorting needs an index and bit to test for folder-ness.
+			const uint8_t ind1 = o1 >> 3, bit1 = o1 & 0x07,
+				ind2 = o2 >> 3, bit2 = o2 & 0x07;
+			#define _SORT_CMP_DIR(fs) \
+										  (((isDir[ind1] & _BV(bit1)) != 0) == ((isDir[ind2] & _BV(bit2)) != 0) \
+											? _SORT_CMP_NODIR() \
+											: (isDir[fs > 0 ? ind1 : ind2] & (fs > 0 ? _BV(bit1) : _BV(bit2))) != 0)
+			#define _SORT_CMP_TIME_DIR(fs) \
+										  (((isDir[ind1] & _BV(bit1)) != 0) == ((isDir[ind2] & _BV(bit2)) != 0) \
+											? _SORT_CMP_TIME_NODIR() \
+											: (isDir[fs > 0 ? ind1 : ind2] & (fs > 0 ? _BV(bit1) : _BV(bit2))) != 0)
+			#else
+			#define _SORT_CMP_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_NODIR() : (fs > 0 ? dir1 : !dir1))
+			#define _SORT_CMP_TIME_DIR(fs) ((dir1 == filenameIsDir) ? _SORT_CMP_TIME_NODIR() : (fs < 0 ? dir1 : !dir1))
+			#endif
+			#endif
+
+			for (uint16_t i = fileCnt; --i;) {
+				if (!IS_SD_INSERTED) return;
+				bool didSwap = false;
+
+				#if !SDSORT_USES_RAM //show progresss bar only if slow sorting method is used
+				int8_t percent = (counter * 100) / total;//((counter * 100) / pow((fileCnt-1),2));
+				for (int column = 0; column < 20; column++) {
+					if (column < (percent / 5)) lcd_implementation_print_at(column, 2, "\x01"); //simple progress bar
+				}
+				counter++;
+				#endif
+
+				//MYSERIAL.println(int(i));
+				for (uint16_t j = 0; j < i; ++j) {
+					if (!IS_SD_INSERTED) return;
+					manage_heater();
+					const uint16_t o1 = sort_order[j], o2 = sort_order[j + 1];
+
+					// The most economical method reads names as-needed
+					// throughout the loop. Slow if there are many.
+					#if !SDSORT_USES_RAM
+					counter++;
+					getfilename_simple(positions[o1]);
+					strcpy(name1, LONGEST_FILENAME); // save (or getfilename below will trounce it)
+					creation_date_bckp = creationDate;
+					creation_time_bckp = creationTime;
+					#if HAS_FOLDER_SORTING
+					bool dir1 = filenameIsDir;
+					#endif
+					getfilename_simple(positions[o2]);
+					char *name2 = LONGEST_FILENAME; // use the string in-place
+
+					#endif // !SDSORT_USES_RAM
+
+													// Sort the current pair according to settings.
+					if (
+					#if HAS_FOLDER_SORTING
+					(sdSort == SD_SORT_TIME && _SORT_CMP_TIME_DIR(FOLDER_SORTING)) || (sdSort == SD_SORT_ALPHA && _SORT_CMP_DIR(FOLDER_SORTING))
+					#else
+						(sdSort == SD_SORT_TIME && _SORT_CMP_TIME_NODIR()) || (sdSort == SD_SORT_ALPHA && _SORT_CMP_NODIR())
+					#endif
+						)
+					{
+						sort_order[j] = o2;
+						sort_order[j + 1] = o1;
+						didSwap = true;
+					}
+				}
+				if (!didSwap) break;
+			} //end of bubble sort loop
+#endif
+			  // Using RAM but not keeping names around
+			#if (SDSORT_USES_RAM && !SDSORT_CACHE_NAMES)
+			#if SDSORT_DYNAMIC_RAM
+			for (uint16_t i = 0; i < fileCnt; ++i) free(sortnames[i]);
+			#if HAS_FOLDER_SORTING
+			free(isDir);
+			#endif
+			#endif
+			#endif
+		}
+		else {
+			sort_order[0] = 0;
+		#if (SDSORT_USES_RAM && SDSORT_CACHE_NAMES)
+			getfilename(0);
+			#if SDSORT_DYNAMIC_RAM
+			sortnames = new char*[1];
+			sortnames[0] = strdup(LONGEST_FILENAME); // malloc
+			sortshort = new char*[1];
+			sortshort[0] = strdup(filename);         // malloc
+			isDir = new uint8_t[1];
+			#else
+			strcpy(sortnames[0], LONGEST_FILENAME);
+			strcpy(sortshort[0], filename);
+			#endif
+			isDir[0] = filenameIsDir ? 0x01 : 0x00;
+		#endif
+		}
+
+		sort_count = fileCnt;
+	}
+#if !SDSORT_USES_RAM //show progresss bar only if slow sorting method is used
+	for (int column = 0; column <= 19; column++)	lcd_implementation_print_at(column, 2, "\x01"); //simple progress bar	
+	delay(300);
+	lcd_set_degree();
+	lcd_implementation_clear();
+	lcd_update(2);
+#endif
+	lcd_update(2);
+	KEEPALIVE_STATE(NOT_BUSY);
+	lcd_timeoutToStatus = millis() + LCD_TIMEOUT_TO_STATUS;
+}
+
+void CardReader::flush_presort() {
+	if (sort_count > 0) {
+		#if SDSORT_DYNAMIC_RAM
+		delete sort_order;
+		#if SDSORT_CACHE_NAMES
+		for (uint8_t i = 0; i < sort_count; ++i) {
+			free(sortshort[i]); // strdup
+			free(sortnames[i]); // strdup
+		}
+		delete sortshort;
+		delete sortnames;
+		#endif
+		#endif
+		sort_count = 0;
+	}
+}
+
+#endif // SDCARD_SORT_ALPHA
+
+
 
 void CardReader::printingHasFinished()
 {
@@ -678,6 +1008,9 @@ void CardReader::printingHasFinished()
           //enquecommand_P(PSTR(SD_FINISHED_RELEASECOMMAND));
       }
       autotempShutdown();
+	  #ifdef SDCARD_SORT_ALPHA
+		  presort();
+	  #endif
     }
 }
 

+ 65 - 0
Firmware/cardreader.h

@@ -31,6 +31,7 @@ public:
   void printingHasFinished();
 
   void getfilename(uint16_t nr, const char* const match=NULL);
+  void getfilename_simple(uint32_t position, const char * const match = NULL);
   uint16_t getnrfilenames();
   
   void getAbsFilename(char *t);
@@ -43,6 +44,19 @@ public:
   void updir();
   void setroot();
 
+  #ifdef SDCARD_SORT_ALPHA
+     void presort();
+	 #ifdef SDSORT_QUICKSORT
+		void swap(uint8_t left, uint8_t right);
+		void quicksort(uint8_t left, uint8_t right);
+	 #endif //SDSORT_QUICKSORT
+     void getfilename_sorted(const uint16_t nr);
+     #if SDSORT_GCODE
+	 FORCE_INLINE void setSortOn(bool b) { sort_alpha = b; presort(); }
+     FORCE_INLINE void setSortFolders(int i) { sort_folders = i; presort(); }
+     //FORCE_INLINE void setSortReverse(bool b) { sort_reverse = b; }
+	 #endif
+  #endif
 
   FORCE_INLINE bool isFileOpen() { return file.isOpen(); }
   FORCE_INLINE bool eof() { return sdpos>=filesize ;};
@@ -62,12 +76,60 @@ public:
   bool sdprinting ;  
   bool cardOK ;
   char filename[13];
+  uint16_t creationTime, creationDate;
+  uint32_t cluster, position;
   char longFilename[LONG_FILENAME_LENGTH];
   bool filenameIsDir;
   int lastnr; //last number of the autostart;
 private:
   SdFile root,*curDir,workDir,workDirParents[MAX_DIR_DEPTH];
   uint16_t workDirDepth;
+
+  // Sort files and folders alphabetically.
+#ifdef SDCARD_SORT_ALPHA
+  uint16_t sort_count;        // Count of sorted items in the current directory
+  #if SDSORT_GCODE
+  bool sort_alpha;          // Flag to enable / disable the feature
+  int sort_folders;         // Flag to enable / disable folder sorting
+							//bool sort_reverse;      // Flag to enable / disable reverse sorting
+  #endif
+
+							// By default the sort index is static
+  #if SDSORT_DYNAMIC_RAM
+  uint8_t *sort_order;
+  #else
+  uint8_t sort_order[SDSORT_LIMIT];
+  #endif
+  // Cache filenames to speed up SD menus.
+  #if SDSORT_USES_RAM
+
+  // If using dynamic ram for names, allocate on the heap.
+  #if SDSORT_CACHE_NAMES
+    #if SDSORT_DYNAMIC_RAM
+      char **sortshort, **sortnames;
+    #else
+      char sortshort[SDSORT_LIMIT][FILENAME_LENGTH];
+      char sortnames[SDSORT_LIMIT][FILENAME_LENGTH];
+    #endif
+  #elif !SDSORT_USES_STACK
+    char sortnames[SDSORT_LIMIT][FILENAME_LENGTH];
+    uint16_t creation_time[SDSORT_LIMIT];
+    uint16_t creation_date[SDSORT_LIMIT];
+  #endif
+
+  // Folder sorting uses an isDir array when caching items.
+  #if HAS_FOLDER_SORTING
+    #if SDSORT_DYNAMIC_RAM
+      uint8_t *isDir;
+    #elif (SDSORT_CACHE_NAMES) || !(SDSORT_USES_STACK)
+      uint8_t isDir[(SDSORT_LIMIT + 7) >> 3];
+    #endif
+  #endif
+
+  #endif // SDSORT_USES_RAM
+
+#endif // SDCARD_SORT_ALPHA
+
   Sd2Card card;
   SdVolume volume;
   SdFile file;
@@ -87,6 +149,9 @@ private:
   int16_t nrFiles; //counter for the files in the current directory and recycled as position counter for getting the nrFiles'th name in the directory.
   char* diveDirName;
   void lsDive(const char *prepend, SdFile parent, const char * const match=NULL);
+#ifdef SDCARD_SORT_ALPHA
+  void flush_presort();
+#endif
 };
 extern CardReader card;
 #define IS_SD_PRINTING (card.sdprinting)

+ 38 - 0
Firmware/language_all.cpp

@@ -422,6 +422,11 @@ const char * const MSG_DATE_LANG_TABLE[LANG_NUM] PROGMEM = {
 	MSG_DATE_CZ
 };
 
+const char MSG_DEFAULT_SETTINGS_LOADED_EN[] PROGMEM = "Default settings loaded";
+const char * const MSG_DEFAULT_SETTINGS_LOADED_LANG_TABLE[1] PROGMEM = {
+	MSG_DEFAULT_SETTINGS_LOADED_EN
+};
+
 const char MSG_DISABLE_STEPPERS_EN[] PROGMEM = "Disable steppers";
 const char MSG_DISABLE_STEPPERS_CZ[] PROGMEM = "Vypnout motory";
 const char * const MSG_DISABLE_STEPPERS_LANG_TABLE[LANG_NUM] PROGMEM = {
@@ -621,6 +626,13 @@ const char * const MSG_FILAMENT_LOADING_T3_LANG_TABLE[LANG_NUM] PROGMEM = {
 	MSG_FILAMENT_LOADING_T3_CZ
 };
 
+const char MSG_FILE_CNT_EN[] PROGMEM = "Some files will not be sorted. Max. No. of files in 1 folder for sorting is 100.";
+const char MSG_FILE_CNT_CZ[] PROGMEM = "Nektere soubory nebudou setrideny. Maximalni pocet souboru pro setrideni je 100.";
+const char * const MSG_FILE_CNT_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_FILE_CNT_EN,
+	MSG_FILE_CNT_CZ
+};
+
 const char MSG_FILE_INCOMPLETE_EN[] PROGMEM = "File incomplete. Continue anyway?";
 const char MSG_FILE_INCOMPLETE_CZ[] PROGMEM = "Soubor nekompletni. Pokracovat?";
 const char * const MSG_FILE_INCOMPLETE_LANG_TABLE[LANG_NUM] PROGMEM = {
@@ -1750,6 +1762,32 @@ const char * const MSG_SOFTWARE_RESET_LANG_TABLE[1] PROGMEM = {
 	MSG_SOFTWARE_RESET_EN
 };
 
+const char MSG_SORTING_EN[] PROGMEM = "Sorting files";
+const char * const MSG_SORTING_LANG_TABLE[1] PROGMEM = {
+	MSG_SORTING_EN
+};
+
+const char MSG_SORT_ALPHA_EN[] PROGMEM = "Sort: [Alphabet]";
+const char MSG_SORT_ALPHA_CZ[] PROGMEM = "Trideni [Abeceda]";
+const char * const MSG_SORT_ALPHA_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_SORT_ALPHA_EN,
+	MSG_SORT_ALPHA_CZ
+};
+
+const char MSG_SORT_NONE_EN[] PROGMEM = "Sort: [None]";
+const char MSG_SORT_NONE_CZ[] PROGMEM = "Trideni [Zadne]";
+const char * const MSG_SORT_NONE_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_SORT_NONE_EN,
+	MSG_SORT_NONE_CZ
+};
+
+const char MSG_SORT_TIME_EN[] PROGMEM = "Sort: [Time]";
+const char MSG_SORT_TIME_CZ[] PROGMEM = "Trideni [Cas]";
+const char * const MSG_SORT_TIME_LANG_TABLE[LANG_NUM] PROGMEM = {
+	MSG_SORT_TIME_EN,
+	MSG_SORT_TIME_CZ
+};
+
 const char MSG_SPEED_EN[] PROGMEM = "Speed";
 const char MSG_SPEED_CZ[] PROGMEM = "Rychlost";
 const char * const MSG_SPEED_LANG_TABLE[LANG_NUM] PROGMEM = {

+ 12 - 0
Firmware/language_all.h

@@ -152,6 +152,8 @@ extern const char* const MSG_CURRENT_LANG_TABLE[LANG_NUM];
 #define MSG_CURRENT LANG_TABLE_SELECT(MSG_CURRENT_LANG_TABLE)
 extern const char* const MSG_DATE_LANG_TABLE[LANG_NUM];
 #define MSG_DATE LANG_TABLE_SELECT(MSG_DATE_LANG_TABLE)
+extern const char* const MSG_DEFAULT_SETTINGS_LOADED_LANG_TABLE[1];
+#define MSG_DEFAULT_SETTINGS_LOADED LANG_TABLE_SELECT_EXPLICIT(MSG_DEFAULT_SETTINGS_LOADED_LANG_TABLE, 0)
 extern const char* const MSG_DISABLE_STEPPERS_LANG_TABLE[LANG_NUM];
 #define MSG_DISABLE_STEPPERS LANG_TABLE_SELECT(MSG_DISABLE_STEPPERS_LANG_TABLE)
 extern const char* const MSG_DWELL_LANG_TABLE[1];
@@ -222,6 +224,8 @@ extern const char* const MSG_FILAMENT_LOADING_T2_LANG_TABLE[LANG_NUM];
 #define MSG_FILAMENT_LOADING_T2 LANG_TABLE_SELECT(MSG_FILAMENT_LOADING_T2_LANG_TABLE)
 extern const char* const MSG_FILAMENT_LOADING_T3_LANG_TABLE[LANG_NUM];
 #define MSG_FILAMENT_LOADING_T3 LANG_TABLE_SELECT(MSG_FILAMENT_LOADING_T3_LANG_TABLE)
+extern const char* const MSG_FILE_CNT_LANG_TABLE[LANG_NUM];
+#define MSG_FILE_CNT LANG_TABLE_SELECT(MSG_FILE_CNT_LANG_TABLE)
 extern const char* const MSG_FILE_INCOMPLETE_LANG_TABLE[LANG_NUM];
 #define MSG_FILE_INCOMPLETE LANG_TABLE_SELECT(MSG_FILE_INCOMPLETE_LANG_TABLE)
 extern const char* const MSG_FILE_PRINTED_LANG_TABLE[1];
@@ -590,6 +594,14 @@ extern const char* const MSG_SLIGHT_SKEW_LANG_TABLE[LANG_NUM];
 #define MSG_SLIGHT_SKEW LANG_TABLE_SELECT(MSG_SLIGHT_SKEW_LANG_TABLE)
 extern const char* const MSG_SOFTWARE_RESET_LANG_TABLE[1];
 #define MSG_SOFTWARE_RESET LANG_TABLE_SELECT_EXPLICIT(MSG_SOFTWARE_RESET_LANG_TABLE, 0)
+extern const char* const MSG_SORTING_LANG_TABLE[1];
+#define MSG_SORTING LANG_TABLE_SELECT_EXPLICIT(MSG_SORTING_LANG_TABLE, 0)
+extern const char* const MSG_SORT_ALPHA_LANG_TABLE[LANG_NUM];
+#define MSG_SORT_ALPHA LANG_TABLE_SELECT(MSG_SORT_ALPHA_LANG_TABLE)
+extern const char* const MSG_SORT_NONE_LANG_TABLE[LANG_NUM];
+#define MSG_SORT_NONE LANG_TABLE_SELECT(MSG_SORT_NONE_LANG_TABLE)
+extern const char* const MSG_SORT_TIME_LANG_TABLE[LANG_NUM];
+#define MSG_SORT_TIME LANG_TABLE_SELECT(MSG_SORT_TIME_LANG_TABLE)
 extern const char* const MSG_SPEED_LANG_TABLE[LANG_NUM];
 #define MSG_SPEED LANG_TABLE_SELECT(MSG_SPEED_LANG_TABLE)
 extern const char* const MSG_STACK_ERROR_LANG_TABLE[1];

+ 5 - 1
Firmware/language_cz.h

@@ -352,4 +352,8 @@
 #define MSG_RECOVER_PRINT					"Detekovan vypadek proudu.Obnovit tisk?"
 #define MSG_PRESS_TO_UNLOAD					"Pro vysunuti filamentu stisknete prosim tlacitko"	
 #define MSG_UNLOAD_SUCCESSFULL				"Opakovat vysunuti filamentu?"
-#define MSG_FILE_INCOMPLETE					"Soubor nekompletni. Pokracovat?"
+#define MSG_FILE_INCOMPLETE					"Soubor nekompletni. Pokracovat?"
+#define MSG_FILE_CNT						"Nektere soubory nebudou setrideny. Maximalni pocet souboru pro setrideni je 100."
+#define MSG_SORT_TIME						"Trideni [Cas]"
+#define MSG_SORT_ALPHA						"Trideni [Abeceda]"
+#define MSG_SORT_NONE						"Trideni [Zadne]"

+ 6 - 0
Firmware/language_en.h

@@ -370,3 +370,9 @@
 #define(length=20, lines=2) MSG_UNLOAD_SUCCESSFULL				"Repeat unloading filament?"
 #define(length=20, lines=2) MSG_FILE_INCOMPLETE					"File incomplete. Continue anyway?"
 
+#define(length=20, lines=4) MSG_DEFAULT_SETTINGS_LOADED			"Default settings loaded"
+#define(length=17, lines=1) MSG_SORT_TIME						"Sort: [Time]"
+#define(length=17, lines=1) MSG_SORT_ALPHA						"Sort: [Alphabet]"
+#define(length=17, lines=1) MSG_SORT_NONE						"Sort: [None]"
+#define(length=20, lines=1) MSG_SORTING							"Sorting files"
+#define(length=20, lines=4) MSG_FILE_CNT						"Some files will not be sorted. Max. No. of files in 1 folder for sorting is 100."

+ 66 - 18
Firmware/ultralcd.cpp

@@ -121,6 +121,10 @@ extern void crashdet_disable();
 uint8_t snmm_extruder = 0;
 #endif
 
+#ifdef SDCARD_SORT_ALPHA
+ bool presort_flag = false;
+#endif
+
 int lcd_commands_type=LCD_COMMAND_IDLE;
 int lcd_commands_step=0;
 bool isPrintPaused = false;
@@ -3236,6 +3240,21 @@ void EEPROM_read(int pos, uint8_t* value, uint8_t size)
   } while (--size);
 }
 
+#ifdef SDCARD_SORT_ALPHA
+static void lcd_sort_type_set() {
+	uint8_t sdSort;
+		EEPROM_read(EEPROM_SD_SORT, (uint8_t*)&sdSort, sizeof(sdSort));
+	switch (sdSort) {
+		case SD_SORT_TIME: sdSort = SD_SORT_ALPHA; break;
+		case SD_SORT_ALPHA: sdSort = SD_SORT_NONE; break;
+		default: sdSort = SD_SORT_TIME;
+	}
+	eeprom_update_byte((unsigned char *)EEPROM_SD_SORT, sdSort);
+	presort_flag = true;
+	lcd_goto_menu(lcd_settings_menu, 8);
+}
+#endif //SDCARD_SORT_ALPHA
+
 static void lcd_silent_mode_set() {
   SilentModeMenu = !SilentModeMenu;
   eeprom_update_byte((unsigned char *)EEPROM_SILENT, SilentModeMenu);
@@ -3291,6 +3310,16 @@ static void lcd_fsensor_state_set()
     
 }
 
+#if !SDSORT_USES_RAM
+void lcd_set_degree() {
+	lcd_set_custom_characters_degree();
+}
+
+void lcd_set_progress() {
+	lcd_set_custom_characters_progress();
+}
+#endif
+
 void lcd_force_language_selection() {
   eeprom_update_byte((unsigned char *)EEPROM_LANG, LANG_ID_FORCE_SELECTION);
 }
@@ -3718,6 +3747,18 @@ static void lcd_settings_menu()
   } else {
     MENU_ITEM(function, MSG_TOSHIBA_FLASH_AIR_COMPATIBILITY_OFF, lcd_toshiba_flash_air_compatibility_toggle);
   }
+
+  #ifdef SDCARD_SORT_ALPHA
+	  if (!farm_mode) {
+	  uint8_t sdSort;
+	  EEPROM_read(EEPROM_SD_SORT, (uint8_t*)&sdSort, sizeof(sdSort));
+	  switch (sdSort) {
+		  case SD_SORT_TIME: MENU_ITEM(function, MSG_SORT_TIME, lcd_sort_type_set); break;
+		  case SD_SORT_ALPHA: MENU_ITEM(function, MSG_SORT_ALPHA, lcd_sort_type_set); break;
+		  default: MENU_ITEM(function, MSG_SORT_NONE, lcd_sort_type_set);
+	  }
+  }
+  #endif // SDCARD_SORT_ALPHA
     
     if (farm_mode)
     {
@@ -5193,8 +5234,12 @@ void getFileDescription(char *name, char *description) {
 
 void lcd_sdcard_menu()
 {
-
-	int tempScrool = 0;
+  uint8_t sdSort = eeprom_read_byte((uint8_t*)EEPROM_SD_SORT);
+  int tempScrool = 0;
+  if (presort_flag == true) {
+	  presort_flag = false;
+	  card.presort();
+  }
   if (lcdDrawUpdate == 0 && LCD_CLICKED == 0)
     //delay(100);
     return; // nothing to do (so don't thrash the SD card)
@@ -5216,22 +5261,24 @@ void lcd_sdcard_menu()
   {
     if (_menuItemNr == _lineNr)
     {
-#ifndef SDCARD_RATHERRECENTFIRST
-      card.getfilename(i);
-#else
-      card.getfilename(fileCnt - 1 - i);
-#endif
-      if (card.filenameIsDir)
-      {
-        MENU_ITEM(sddirectory, MSG_CARD_MENU, card.filename, card.longFilename);
-      } else {
-
-        MENU_ITEM(sdfile, MSG_CARD_MENU, card.filename, card.longFilename);
-
-
-
-
-      }
+		const uint16_t nr = ((sdSort == SD_SORT_NONE) || farm_mode || (sdSort == SD_SORT_TIME)) ? (fileCnt - 1 - i) : i;
+		/*#ifdef SDCARD_RATHERRECENTFIRST
+			#ifndef SDCARD_SORT_ALPHA
+				fileCnt - 1 -
+			#endif
+		#endif
+		i;*/
+		#ifdef SDCARD_SORT_ALPHA
+			if (sdSort == SD_SORT_NONE) card.getfilename(nr);
+			else card.getfilename_sorted(nr);
+		#else
+			 card.getfilename(nr);
+		#endif
+			
+		if (card.filenameIsDir)
+			MENU_ITEM(sddirectory, MSG_CARD_MENU, card.filename, card.longFilename);
+		else
+			MENU_ITEM(sdfile, MSG_CARD_MENU, card.filename, card.longFilename);
     } else {
       MENU_ITEM_DUMMY();
     }
@@ -6460,6 +6507,7 @@ void lcd_update(uint8_t lcdDrawUpdateOverride)
         (*currentMenu)();
         menuExiting = false;
       }
+	      lcd_implementation_clear();
 		  lcd_return_to_status();
 		  lcdDrawUpdate = 2;
 	  }

+ 8 - 0
Firmware/ultralcd.h

@@ -238,6 +238,9 @@ void extr_unload_all();
 void extr_unload_used();
 void extr_unload();
 static char snmm_stop_print_menu();
+#ifdef SDCARD_SORT_ALPHA
+ static void lcd_sort_type_set();
+#endif
 static float count_e(float layer_heigth, float extrusion_width, float extrusion_length);
 static void lcd_babystep_z();
 
@@ -272,6 +275,11 @@ void display_loading();
 
 void lcd_service_mode_show_result();
 
+#if !SDSORT_USES_RAM
+ void lcd_set_degree();
+ void lcd_set_progress();
+#endif
+
 void lcd_wizard();
 void lcd_wizard(int state);
 

+ 24 - 8
Firmware/ultralcd_implementation_hitachi_HD44780.h

@@ -462,6 +462,21 @@ void lcd_set_custom_characters_arrows()
     lcd.createChar(1, arrdown);
 }
 
+void lcd_set_custom_characters_progress()
+ {
+  byte progress[8] = {
+	B11111,
+	B11111,
+	B11111,
+	B11111,
+	B11111,
+	B11111,
+	B11111,
+	B11111,
+  };
+  lcd.createChar(1, progress);
+ }
+
 void lcd_set_custom_characters_nextpage()
  {
 
@@ -847,17 +862,18 @@ if (print_sd_status)
     // If heating in progress, set flag
 	if (heating_status != 0) { custom_message = true; }
 
+	if (IS_SD_PRINTING) {
+		if (strcmp(longFilenameOLD, card.longFilename) != 0)
+		{
+			memset(longFilenameOLD, '\0', strlen(longFilenameOLD));
+			sprintf_P(longFilenameOLD, PSTR("%s"), card.longFilename);
+			scrollstuff = 0;
+		}
+	}
+
     // If printing from SD, show what we are printing
 	if ((IS_SD_PRINTING) && !custom_message)
 	{
-
-      if(strcmp(longFilenameOLD, card.longFilename) != 0)
-	  {
-        memset(longFilenameOLD,'\0',strlen(longFilenameOLD));
-        sprintf_P(longFilenameOLD, PSTR("%s"), card.longFilename);
-        scrollstuff = 0;
-      }
-
       if(strlen(card.longFilename) > LCD_WIDTH)
 	  {