Skip to content

Instantly share code, notes, and snippets.

@MattSturgeon
Last active July 8, 2016 19:55
Show Gist options
  • Save MattSturgeon/5cadb42f8caa126e0afa21d55f868eff to your computer and use it in GitHub Desktop.
Save MattSturgeon/5cadb42f8caa126e0afa21d55f868eff to your computer and use it in GitHub Desktop.
Extract filetype filters
#include <gtk/gtk.h>
#include <string>
#include <array>
#include <cstring> // For memcpy
#include <iostream> // For cerr (if unknown special)
#include "platform.h"
/* Add a GtkFileFilter object for each row in f_types to the given GtkFileChooser */
void add_gtk_filters(GtkFileChooser *dialog) {
for (size_t i = 0; i < f_types.size(); i++) {
GtkFileFilter *filter = gtk_file_filter_new();
/* Set the name */
gtk_file_filter_set_name(filter, f_types.at(i).name.c_str());
if (f_types.at(i).special) {
/* Special cases */
if (f_types.at(i).type == "ALL_TYPES") {
/* For ALL_TYPES use "*" pattern to match any file */
gtk_file_filter_add_pattern(filter, "*");
}
else if (f_types.at(i).type == "ALL_SUPPORTED") {
/* For ALL_SUPPORTED add a pattern for each supported file type */
for (size_t j = 0; j < f_types.size(); j++) {
if (! f_types.at(j).special) {
// Use standard pattern "*.type"
gtk_file_filter_add_pattern(filter, ("*." + f_types.at(j).type).c_str());
}
}
}
else {
/* Unknown special! Just continue to the next file_type. */
std::cerr << "Warning: Unknown special file_type " << f_types.at(i).type << "at index " << i << std::endl;
// Free filter?
continue;
}
}
else {
/* Normal values */
// For normal file types, just add the standard pattern "*.type"
gtk_file_filter_add_pattern(filter, ("*." + f_types.at(i).type).c_str());
}
// Add the filter
gtk_file_chooser_add_filter(dialog, filter);
}
}
char *show_file_picker() {
char *path = nullptr;
GtkWidget *parent, *dialog;
if (!gtk_init_check(NULL, NULL))
return nullptr;
parent = gtk_window_new(GTK_WINDOW_TOPLEVEL);
dialog = gtk_file_chooser_dialog_new( "Open File",
GTK_WINDOW(parent),
GTK_FILE_CHOOSER_ACTION_OPEN,
"_Cancel", GTK_RESPONSE_CANCEL,
"_Open", GTK_RESPONSE_ACCEPT,
NULL );
// Filter file types
add_gtk_filters(GTK_FILE_CHOOSER (dialog)); // NOTE: This is the only change to this function
if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
char *filename = nullptr;
filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
size_t len = strlen(filename);
path = (char *)malloc(len+1);
memcpy(path, filename, len+1);
if (!path ) {
g_free(filename);
gtk_widget_destroy(dialog);
return nullptr;
}
g_free(filename);
}
while (gtk_events_pending())
gtk_main_iteration();
gtk_widget_destroy(dialog);
while (gtk_events_pending())
gtk_main_iteration();
return path;
}
//#include "platform.h"
#include <array>
#include <string>
#include "platform.h"
// For debugging (std::cout, std::cerr)
#include <iostream>
#include <cstdio>
int main() {
/* First print our internal data */
printf("\nPrinting std::array <file_type> f_types...\n\n");
// Print entire contents of f_types
std::cout << "f_types[" << f_types.size() << "]: {{" << std::endl;
for (size_t i = 0; i < f_types.size(); i++) {
std::cout << " " << i << ": {" << std::endl
<< " name: \"" << f_types.at(i).name << "\"," << std::endl
<< " type: \"" << f_types.at(i).type << "\"," << std::endl
<< " special: " << f_types.at(i).special << "," << std::endl
<< " }," << std::endl;
}
std::cout << "}}" << std::endl;
/* Next generate and print lpstrFilter */
printf("\n\nRunning (win32) gen_lpstrFilter...\n");
// Alocate 512 bytes to out
size_t winFilterArr_len = 512;
char winFilterArr[winFilterArr_len] = {0};
size_t bytes_written = gen_lpstrFilter(winFilterArr, winFilterArr_len);
// Print each string from winFilterArr until we hit an empty string
printf("\n\nwinFilterArr[%lu of %lu]:\n \"", bytes_written, winFilterArr_len);
size_t i = 0, x = 0;
int y = 1, z;
while (winFilterArr[x] || winFilterArr[x-1]) { // If x and x-1 point to '\0' this is an empty string
// Print each char, replace '\0' with \n
if (winFilterArr[x])
// Print the x'th char from winFilterArr.
fwrite( &winFilterArr[x], sizeof(winFilterArr[x]), 1, stdout);
else{
// "End" the string
printf("\"");
// If not the last, prep the next string (indentation and quotes)
if (winFilterArr[x+1]) {
if (i % 2 > 0) printf("\n \"");
else {
// Align second string ~36 chars from begining of left string
z = 36 - y; if (z < 1) z = 1;
while (--z) printf(" ");
printf("\"");
}
}
i++; y = 0;
}
x++; y++;
}
printf("\n");
/* Next Create a Gtk file picker and print the returned path to stdout */
printf("\n\nRunning (gtk) show_file_picker... \n\n");
printf("Gtk filepath: \"%s\"\n\n", show_file_picker());
return 0;
}
CC=g++
CPPFLAGS=-I. `pkg-config --cflags --libs gtk+-3.0`
demo: main.o gtk.o win32.o
$(CC) $(CPPFLAGS) -o demo main.o gtk.o win32.o -I.
clean:
rm -vf demo *.o
#ifndef PLATFORM_H
#define PLATFORM_H
#include <array>
#include <string>
/*
* file_type is used to describe a file type to be filtered by the
* platform's "open" dialog.
*
* 'name' is the user-visible description given for the generated filter.
* 'type' is the file extension that should be filtered.
* 'special' should be true if mapping functions need to write code specific
* to generating a filter for this file_type (e.g. ALL_TYPES).
*
*/
struct file_type {
const std::string type;
const std::string name;
const bool special;
};
/*
* f_types is an array of file_type structs.
*
* The first entries are special, and have special handling rules in the
* various mapping functions.
*
*/
static std::array <file_type, 5> f_types = {{
/* Special values */
{
/* ALL_SUPPORTED should match all non-special f_types in this array. */
type: "ALL_SUPPORTED", name: "Supported files",
special: true,
},{
/* ALL_TYPES should match any file */
type: "ALL_TYPES", name: "All files",
special: true, // If OSX supports '*', this will not need to be special
},
/* Normal filters */
{ type: "brd", name: "BRD files - the origionals!", },
{ type: "bdv", name: "BDV files are cool too...", },
{ type: "fz", name: "FZ... Ok, who even is this guy?", },
}};
/* Function declarations for the main.cpp demo */
size_t gen_lpstrFilter (char * out, size_t out_len);
char *show_file_picker();
#endif /* end of include guard: PLATFORM_H */
#include <string>
#include <array>
#include <cstring> // For memcpy
#include <iostream> // For cerr (if unknown special)
#include "platform.h"
/* Generate a windows lpstrFilter array for show_file_picker.
*
* out's type should probably be corrected to follow standard windows
* definition LPSTR.
*
* Writes a packed array of pairs of null terminated char arrays.
* Each pair is a Filter name and a filter list.
* After the final pair an empty string ("\0") is added to terminate
* the array.
*
* Takes a char pointer and the max number of bytes to write.
*
* Will check to make sure it has enough space before writing each pair.
* If it runs out of space it will print to stderr and exit (1). This
* should probably be amended to show a dialog warning and then return 0.
* returns the number of bytes written to out
*/
size_t gen_lpstrFilter (char * out, size_t out_len) {
// Keep track of how much we've written to out.
// We use this to decide where to write the next string
// and to double check we have enough memory left.
size_t x = 0;
// lpstrFilter is a blob of memory containing null-terninated strings
// with an extra null to terminate the blob.
// i.e. it's a packed-array of null-terninated strings with a zero-length
// string at the end.
// It's read as pairs of strings, with the first being the name and the
// second being a semi-colon seperated list of filters.
// Iterate over each file_type
for (size_t i = 0; i < f_types.size(); i++) {
// Set the name and define filter for current item
std::string name = f_types.at(i).name;
std::string filter;
if (f_types.at(i).special) {
/* Special cases */
if (f_types.at(i).type == "ALL_TYPES") {
/* For ALL_TYPES use "*" filter to match any file */
filter = "*";
}
else if (f_types.at(i).type == "ALL_SUPPORTED") {
/* For ALL_SUPPORTED create a list each supported file type */
for (size_t j = 0; j < f_types.size(); j++) {
// Only add non-special items
if (! f_types.at(j).special){
// Add ";*.file_type" to filter
if (filter.length()) filter += ";"; // Don't prefix ';' to the first item
filter += "*." + f_types.at(j).type;
}
}
}
else {
/* Unknown special! Just continue to the next file_type. */
std::cerr << "Warning: Unknown special file_type " << f_types.at(i).type << "at index " << i << std::endl;
continue;
}
}
else {
/* Normal values */
// For normal file types, just add the standard pattern "*.type"
filter = "*." + f_types.at(i).type;
}
// Make sure we have enouth space left in 'out' to fit 'filter', 'name' and three '\0's.
// Two of the '\0's terminate the strings (filter and name - string::length doesnt count '\0's)
// The third terminates the empty string that ends the lpstrFilter array.
if (x >= out_len - (filter.length() + name.length() + 3)) { // +3 null chars (1 ends filter, 1 ends name, 1 ends the array)
// Run out of buffer memory!!!
fprintf(stderr, "Error: Ran out of memory to store out!\n i: '%lu', x: '%lu', out_len: '%lu'\n", i, x, out_len);
exit (1);
}
// Copy the pair of strings
//
// (char *)& out[x] creates a char pointer to x bytes after the start of out
// memcpy then copies name to position-x
// finally we add the number of bytes written, to x (+1 for null-ternination)
// First string is name
memcpy((char *)&out[x], name.c_str(), name.length());
x += name.length() + 1;
// Second string is filter
memcpy((char *)&out[x], filter.c_str(), filter.length());
x += filter.length() + 1;
}
// Ensure there is a final empty string to terminate the packed array
memcpy((char *)&out[x], "", 1);
x++;
return x;
}
@piernov
Copy link

piernov commented Jul 8, 2016

For Mac OS X you can do something like this: (diff from Charliebruce's repo, master branch)

diff --git a/platform_osx.mm b/platform_osx.mm
index 324bec4..c35b383 100644
--- a/platform_osx.mm
+++ b/platform_osx.mm
@@ -1,15 +1,23 @@
 #ifdef __APPLE__

 #include <string>
+#include "platform.h"
 #import <Cocoa/Cocoa.h>

 char *show_file_picker() {
        std::string filename;
+       id file_types = [NSMutableArray new];
+
+       for (auto &f_type: f_types) {
+               id type = [NSString stringWithUTF8String:f_type.type.c_str()];
+               [file_types addObject:type];
+       }
+
        NSOpenPanel *op = [NSOpenPanel openPanel];

        // Filter file types
        [op setCanChooseFiles:YES];
-       [op setAllowedFileTypes:@[@"brd"]];
+       [op setAllowedFileTypes:file_types];

        if ([op runModal] == NSModalResponseOK) {
                NSURL *nsurl = [[op URLs] objectAtIndex:0];

However, (as always with Apple) it's not like on the other platforms: it can only "grey out" the files with extension not in the filter list and you can't select a specific filter. Selecting a specific filter could be implemented with the help of an Accessory View, but it wouldn't be really useful.

PS: note the use of a range-based for loop. A pretty nice feature of C++11.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment