├── README.md └── src ├── Screenshot1.png ├── Screenshot2.png ├── ball.cpp ├── ball.h ├── bg.bmp ├── eventHandling.cpp ├── eventHandling.h ├── gui.cpp ├── gui.h ├── imageloader.cpp ├── imageloader.h ├── main.cpp ├── makefile ├── tinyfiledialogs.c └── tinyfiledialogs.h /README.md: -------------------------------------------------------------------------------- 1 | 2 | Bouncing-Ball-Animation 3 | ======================== 4 | 5 | - A c++ project simulating collision between balls in 2D and 3D. 6 | - Built using openGL, glut and glui libraries. 7 | - Glui implements a very simple and sover GUI which allow users to control different attributes of the balls. 8 | 9 | 10 | 11 | Features 12 | ======== 13 | 14 | - User can play/pause the scene, change number of balls, change color of balls, increase/drcrease speed of balls. 15 | - User can select a particular ball by clicking on it and then change its corresponding attributes. 16 | - If no ball is selected then attributes of all the balls will change. 17 | - Supports four "Look & Feel" options - Default, Metallic, High Contrast, Pool/Billiard 18 | - 2D and 3D mode. 19 | 20 | - Here is a screenshot of the application in the 2D mode. 21 | 22 | ![alt text][screenshot2d] 23 | 24 | [screenshot2d]: ./src/Screenshot1.png "Screenshot2d" 25 | 26 | - Here is a screenshot of the application in the 3D mode. 27 | 28 | ![alt text][screenshot3d] 29 | 30 | [screenshot3d]: ./src/Screenshot2.png "Screenshot3d" 31 | 32 | 33 | 34 | Instructions 35 | ============ 36 | For compiling use make command. Once the program is running, following holds, 37 | 38 | 1. Space Bar will toggle Play/Pause. 39 | 2. In 3D mode, dragging the mouse will change orientation of the cuboid and scrolling will zoom in/out. 40 | 3. In 3D mode, user can move the cuboid by arrow keys. 41 | 4. Enter and Esc key will quit the program. 42 | 43 | 44 | Any further suggestions are welcome. Give your feedback and criticism at - amanbhatia2510@gmail.com 45 | -------------------------------------------------------------------------------- /src/Screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-bhatia/Bouncing-Ball-Animation/ad0de3e2e56264360a3d943bafff3850088ea7e8/src/Screenshot1.png -------------------------------------------------------------------------------- /src/Screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-bhatia/Bouncing-Ball-Animation/ad0de3e2e56264360a3d943bafff3850088ea7e8/src/Screenshot2.png -------------------------------------------------------------------------------- /src/ball.cpp: -------------------------------------------------------------------------------- 1 | #include "ball.h" 2 | 3 | #include 4 | #include 5 | #define PI 3.14156 6 | using namespace std; 7 | 8 | int max_balls = 10; 9 | int num_balls = max_balls; 10 | double min_radius = 0.2; 11 | int threeD = 0; 12 | float pers_angle = 30.0; 13 | float z_plane = 6.0; 14 | float wly = tan(pers_angle * PI / 360) * z_plane; 15 | float wlx; 16 | float globalR = 255; 17 | float globalG = 0; 18 | float globalB = 0; 19 | float min_speed = 0.003; 20 | float max_speed = 0.1; 21 | 22 | const int num_colors = 7; 23 | color clrArr[num_colors] = {{1,0,0},//red 24 | {0,1,0},//green 25 | {0,0,0.625},//dark blue 26 | { 233.0/256, 171.0/256, 23.0/256},//yellow 27 | {0.5, 0,0},// brown 28 | {1,0.5, 0.25},//orange 29 | {75.0/256, 0, 130.0/256}//purple 30 | }; 31 | 32 | //---------------------------------------------------------Ball class-------------------------------------------------------------// 33 | 34 | // it will return a value between -wly to +wly 35 | float ran(){ 36 | return (((-wly*10000) + rand()%((int)(2*wly*10000)))/10000); 37 | } 38 | 39 | ball::ball(int bid){ 40 | id = bid; 41 | bclr = clrArr[rand()%num_colors]; 42 | } 43 | ball::ball() {}; 44 | int ball::getId(){ 45 | return id; 46 | } 47 | color ball::getColor(){ 48 | return bclr; 49 | } 50 | void ball::setColor(color cl){ 51 | bclr = cl; 52 | } 53 | void ball::setColor(){ 54 | bclr = clrArr[rand()%num_colors]; 55 | } 56 | void ball::setCentreX(float cenX){ 57 | cx = cenX; 58 | } 59 | void ball::setCentreY(float cenY){ 60 | cy = cenY; 61 | } 62 | void ball::setCentreZ(float cenZ){ 63 | cz = cenZ; 64 | } 65 | float ball::getCentreX(){ 66 | return cx; 67 | } 68 | float ball::getCentreY(){ 69 | return cy; 70 | } 71 | float ball::getCentreZ(){ 72 | return cz; 73 | } 74 | void ball::setVelocityX(float velX){ 75 | vx = velX; 76 | } 77 | void ball::setVelocityY(float velY){ 78 | vy = velY; 79 | } 80 | void ball::setVelocityZ(float velZ){ 81 | vz = velZ; 82 | } 83 | float ball::getVelocityX(){ 84 | return vx; 85 | } 86 | float ball::getVelocityY(){ 87 | return vy; 88 | } 89 | float ball::getVelocityZ(){ 90 | return vz; 91 | } 92 | void ball::setRadius(float rd){ 93 | if (rd wlx){ 124 | cx = wlx - rad; 125 | vx = -vx; 126 | } else if (cx - rad < -wlx){ 127 | cx = -wlx + rad; 128 | vx = -vx; 129 | } else if (cy + rad > wly){ 130 | cy = wly - rad; 131 | vy = -vy; 132 | }else if (cy - rad < -wly){ 133 | cy = -wly + rad; 134 | vy = -vy; 135 | } 136 | if (threeD){ 137 | if (cz + rad > wly){ 138 | cz = wly - rad; 139 | vz = -vz; 140 | }else if (cz - rad < -wly){ 141 | cz = -wly + rad; 142 | vz = -vz; 143 | } 144 | } 145 | } 146 | // handle the collision between two balls 147 | void ball:: handleBallBallCollision(ball &bl){ 148 | if (threeD){ 149 | float dx = cx - bl.getCentreX(); 150 | float dy = cy - bl.getCentreY(); 151 | float dz = cz - bl.getCentreZ(); 152 | float dbc = sqrt(dx*dx + dy*dy + dz*dz); // distance between centres 153 | float sor = rad + bl.getRadius(); // sum of radii 154 | 155 | // minimum translation distance to push balls apart after intersecting 156 | float mtdx = dx * ((sor - dbc)/dbc); 157 | float mtdy = dy * ((sor - dbc)/dbc); 158 | float mtdz = dz * ((sor - dbc)/dbc); 159 | 160 | // inverse mass quantities 161 | float im1 = 1 / rad*rad*rad; 162 | float im2 = 1 / bl.getRadius()*bl.getRadius()*bl.getRadius(); 163 | 164 | // push-pull them apart based off their mass 165 | cx += (mtdx * (im1 / (im1 + im2))); 166 | cy += (mtdy * (im1 / (im1 + im2))); 167 | cz += (mtdz * (im1 / (im1 + im2))); 168 | 169 | bl.moveX(-(mtdx * (im2 / (im1 + im2)))); 170 | bl.moveY(-(mtdy * (im2 / (im1 + im2)))); 171 | bl.moveZ(-(mtdz * (im2 / (im1 + im2)))); 172 | 173 | // impact speed 174 | float dvx = vx - bl.getVelocityX(); 175 | float dvy = vy - bl.getVelocityY(); 176 | float dvz = vz - bl.getVelocityZ(); 177 | float vn = (dvx * (mtdx / sqrt(mtdx*mtdx + mtdy*mtdy + mtdz*mtdz))) + (dvy * (mtdy / sqrt(mtdx*mtdx + mtdy*mtdy + mtdz*mtdz))) + (dvz * (mtdz / sqrt(mtdx*mtdx + mtdy*mtdy + mtdz*mtdz))); 178 | 179 | // sphere intersecting but moving away from each other already 180 | if (vn > 0) return; 181 | 182 | // impact speed 183 | float m1 = 1/im1; 184 | float m2 = 1/im2; 185 | 186 | float th = atan((cy - bl.getCentreY()) / (cx - bl.getCentreX())); 187 | float cth = cosf(th); 188 | float sth = sinf(th); 189 | 190 | // velocities in direction of centres 191 | float v1t = (cth*((m1-m2)*vx + 2*m2*bl.getVelocityX()) + sth*((m1-m2)*vy + 2*m2*bl.getVelocityY()))/(m1+m2); 192 | float v2t = v1t + (cth*(vx - bl.getVelocityX()) + sth*(vy - bl.getVelocityY())); 193 | float v1n = (vy*cth) - (sth*vx); 194 | float v2n = (cth*bl.getVelocityY() - sth*bl.getVelocityX()); 195 | 196 | vx = (v1t*cth) - (v1n*sth); 197 | vy = (v1n*cth) + (v1t*sth); 198 | bl.setVelocityX((v2t*cth) - (v2n*sth)); 199 | bl.setVelocityY((v2n*cth) + (v2t*sth)); 200 | } else { 201 | float dx = cx - bl.getCentreX(); 202 | float dy = cy - bl.getCentreY(); 203 | float dbc = sqrt(dx*dx + dy*dy); // distance between centres 204 | float sor = rad + bl.getRadius(); // sum of radii 205 | 206 | // minimum translation distance to push balls apart after intersecting 207 | float mtdx = dx * ((sor - dbc)/dbc); 208 | float mtdy = dy * ((sor - dbc)/dbc); 209 | 210 | // inverse mass quantities 211 | float im1 = 1 / rad*rad; 212 | float im2 = 1 / bl.getRadius()*bl.getRadius(); 213 | 214 | // push-pull them apart based off their mass 215 | cx += (mtdx * (im1 / (im1 + im2))); 216 | cy += (mtdy * (im1 / (im1 + im2))); 217 | bl.moveX(-(mtdx * (im2 / (im1 + im2)))); 218 | bl.moveY(-(mtdy * (im2 / (im1 + im2)))); 219 | 220 | // impact speed 221 | float dvx = vx - bl.getVelocityX(); 222 | float dvy = vy - bl.getVelocityY(); 223 | float vn = (dvx * (mtdx / sqrt(mtdx*mtdx + mtdy*mtdy))) + (dvy * (mtdy / sqrt(mtdx*mtdx + mtdy*mtdy))); 224 | 225 | // sphere intersecting but moving away from each other already 226 | if (vn > 0) return; 227 | 228 | // impact speed 229 | float m1 = 1/im1; 230 | float m2 = 1/im2; 231 | 232 | float th = atan((cy - bl.getCentreY()) / (cx - bl.getCentreX())); 233 | float cth = cosf(th); 234 | float sth = sinf(th); 235 | 236 | // velocities in direction of centres 237 | float v1t = (cth*((m1-m2)*vx + 2*m2*bl.getVelocityX()) + sth*((m1-m2)*vy + 2*m2*bl.getVelocityY()))/(m1+m2); 238 | float v2t = v1t + (cth*(vx - bl.getVelocityX()) + sth*(vy - bl.getVelocityY())); 239 | float v1n = (vy*cth) - (sth*vx); 240 | float v2n = (cth*bl.getVelocityY() - sth*bl.getVelocityX()); 241 | 242 | vx = (v1t*cth) - (v1n*sth); 243 | vy = (v1n*cth) + (v1t*sth); 244 | bl.setVelocityX((v2t*cth) - (v2n*sth)); 245 | bl.setVelocityY((v2n*cth) + (v2t*sth)); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/ball.h: -------------------------------------------------------------------------------- 1 | #ifndef BALL_H 2 | #define BALL_H 3 | 4 | extern int max_balls; 5 | extern int num_balls; 6 | extern double min_radius; 7 | extern int threeD; 8 | extern float pers_angle; 9 | extern float z_plane; 10 | extern float wly; 11 | extern float wlx; 12 | extern float globalR; 13 | extern float globalG; 14 | extern float globalB; 15 | extern float min_speed; 16 | extern float max_speed; 17 | 18 | struct color{ 19 | float r,g,b; 20 | }; 21 | extern const int num_colors; 22 | 23 | float ran(); 24 | 25 | class ball{ 26 | private: 27 | int id; 28 | color bclr; 29 | float cx,cy,cz,vx,vy,vz,rad; 30 | public: 31 | ball(int bid); 32 | ball(); 33 | int getId(); 34 | color getColor(); 35 | void setColor(color cl); 36 | void setColor(); 37 | void setCentreX(float cenX); 38 | void setCentreY(float cenY); 39 | void setCentreZ(float cenZ); 40 | float getCentreX(); 41 | float getCentreY(); 42 | float getCentreZ(); 43 | void setVelocityX(float velX); 44 | void setVelocityY(float velY); 45 | void setVelocityZ(float velZ); 46 | float getVelocityX(); 47 | float getVelocityY(); 48 | float getVelocityZ(); 49 | void setRadius(float rd); 50 | float getRadius(); 51 | void moveX(float dx); 52 | void moveY(float dy); 53 | void moveZ(float dz); 54 | bool isBallCollision(ball &bl); 55 | void handleBallWallCollision(); 56 | void handleBallBallCollision(ball &bl); 57 | }; 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/bg.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aman-bhatia/Bouncing-Ball-Animation/ad0de3e2e56264360a3d943bafff3850088ea7e8/src/bg.bmp -------------------------------------------------------------------------------- /src/eventHandling.cpp: -------------------------------------------------------------------------------- 1 | #include "eventHandling.h" 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | int winw = 1200; // intial width of the window 8 | int winh = 680; // intial height of the window 9 | bool playing = true; 10 | int speed = 5; 11 | int prev_speed = 5; 12 | int ball_selected = -1; 13 | int same_size = 1; 14 | int same_color = 0; 15 | int last_x, last_y; 16 | float rotationX = 0.0, rotationY = 0.0; 17 | float scale = 1.0; 18 | 19 | theme thm = Default; 20 | int thm_int = 0; 21 | 22 | 23 | float view_rotate[16] = { 1,0,0,0, 24 | 0,1,0,0, 25 | 0,0,1,-5, 26 | 0,0,0,1 }; 27 | float obj_pos[] = { 0.0, 0.0, 0.0 }; 28 | 29 | /** Pointers to the windows and some of the controls we'll create **/ 30 | GLUI *glui, *glui2; 31 | GLUI_RadioGroup *radio2D, *radio3D, *radio; 32 | GLUI_Panel *obj_panel; 33 | GLUI_Scrollbar* sbRed, *sbGreen, *sbBlue; 34 | GLUI_Button *gb, *gb_enable, *gb_disable; 35 | 36 | ball* Balls; 37 | void makeBalls(void); 38 | 39 | Mouse TheMouse = {0,0,0,0,0}; 40 | 41 | // this functions is triggered at every mouse click and checks whether click occurs on a ball or not 42 | bool ClickTest(ball* b,int x,int y) { 43 | float w = winw-180; 44 | float h = winh - 80; 45 | float scx = (w*(b->getCentreX() + wlx))/(2*wlx); 46 | float scy = h-(h*(b->getCentreY() + wly))/(2*wly); 47 | float srad = h * (b->getRadius() / (2*wly)); 48 | 49 | if( x > (scx-srad) && 50 | x < (scx+srad) && 51 | y > (scy-srad) && 52 | y < (scy+srad) ) { 53 | return true; 54 | } 55 | return false; 56 | } 57 | 58 | // changes attributes of TheMouse accordingly 59 | void MouseButton(int button,int state,int x, int y){ 60 | if (threeD){ 61 | if ((button == 3) || (button == 4)){ // It's a wheel event 62 | // Each wheel event reports like a button click, GLUT_DOWN then GLUT_UP 63 | if (state == GLUT_UP) // Disregard redundant GLUT_UP events 64 | (button == 3) ? (scale -= 0.25) : (scale += 0.25); 65 | } 66 | 67 | if ( button == GLUT_LEFT_BUTTON && state == GLUT_DOWN ) { 68 | last_x = x; 69 | last_y = y; 70 | } 71 | } 72 | TheMouse.x = x; 73 | TheMouse.y = y; 74 | 75 | if (state == GLUT_DOWN) { 76 | TheMouse.xpress = x; 77 | TheMouse.ypress = y; 78 | switch(button) { 79 | case GLUT_LEFT_BUTTON: 80 | TheMouse.lmb = 1; 81 | case GLUT_MIDDLE_BUTTON: 82 | TheMouse.mmb = 1; 83 | break; 84 | case GLUT_RIGHT_BUTTON: 85 | TheMouse.rmb = 1; 86 | break; 87 | } 88 | } else { 89 | switch(button) { 90 | case GLUT_LEFT_BUTTON: 91 | TheMouse.lmb = 0; 92 | break; 93 | case GLUT_MIDDLE_BUTTON: 94 | TheMouse.mmb = 0; 95 | break; 96 | case GLUT_RIGHT_BUTTON: 97 | TheMouse.rmb = 0; 98 | break; 99 | } 100 | } 101 | // check ball selection 102 | ball_selected = -1; 103 | for (int i=0;idisable(); 245 | radio3D->enable(); 246 | } else { 247 | radio2D->enable(); 248 | radio3D->disable(); 249 | } 250 | switch(thm_int){ 251 | case (0): 252 | cbDefault(); 253 | break; 254 | case (1): 255 | cbBlackMetallic(); 256 | break; 257 | case (2): 258 | cbHighContrast(); 259 | break; 260 | case (3): 261 | cbPool(); 262 | break; 263 | } 264 | } 265 | 266 | 267 | // call back function when size changes 268 | void cbSameSize(int value){ 269 | if (same_size){ 270 | for (int i=0;ienable(); 281 | sbGreen->enable(); 282 | sbBlue->enable(); 283 | gb->enable(); 284 | 285 | color clr = {globalR/256,globalG/256,globalB/256}; 286 | for (int i=0;idisable(); 291 | sbGreen->disable(); 292 | sbBlue->disable(); 293 | gb->disable(); 294 | 295 | for (int i=0;ienable(); 302 | sbGreen->enable(); 303 | sbBlue->enable(); 304 | gb->enable(); 305 | 306 | color clr = {(float)globalR/256,(float)globalG/256,(float)globalB/256}; 307 | Balls[ball_selected].setColor(clr); 308 | } else { 309 | sbRed->disable(); 310 | sbGreen->disable(); 311 | sbBlue->disable(); 312 | gb->disable(); 313 | 314 | Balls[ball_selected].setColor(); 315 | } 316 | } 317 | } 318 | 319 | // call back function when mode (2D/3D) changes 320 | void cbMode(int value){ 321 | if(threeD){ 322 | gb_enable->enable(); 323 | gb_disable->enable(); 324 | } else { 325 | gb_enable->disable(); 326 | gb_disable->disable(); 327 | } 328 | makeBalls(); 329 | cbSameSize(1); 330 | cbSameColor(1); 331 | cbTheme(1); 332 | glutPostRedisplay(); 333 | } 334 | 335 | // call back function for chosing color 336 | void cbColorChooser(int value){ 337 | unsigned char ret[3] = {(unsigned char)globalR,(unsigned char)globalG,(unsigned char)globalB}; 338 | tinyfd_colorChooser("Choose Color", NULL,ret,ret); 339 | globalR = ret[0]; 340 | globalG = ret[1]; 341 | globalB = ret[2]; 342 | cbSameColor(1); 343 | } 344 | 345 | // call back function for changing speed 346 | void cbSpeed(int value){ 347 | if (ball_selected == -1){ 348 | if (speed > prev_speed){ 349 | int count = speed - prev_speed; 350 | while (count--) cbIncSpeed(); 351 | } else if (speed < prev_speed){ 352 | int count = prev_speed - speed; 353 | while (count--) cbDecSpeed(); 354 | } 355 | prev_speed = speed; 356 | } else { 357 | if (speed > prev_speed){ 358 | int count = speed - prev_speed; 359 | while (count--) cbIncBallSpeed(ball_selected); 360 | } else if (speed < prev_speed){ 361 | int count = prev_speed - speed; 362 | while (count--) cbDecBallSpeed(ball_selected); 363 | } 364 | prev_speed = speed; 365 | } 366 | 367 | } 368 | 369 | void control_cb( int control ){ 370 | if ( control == ENABLE_ID ){ 371 | glui2->enable(); 372 | } else if ( control == DISABLE_ID ){ 373 | glui2->disable(); 374 | } 375 | } 376 | -------------------------------------------------------------------------------- /src/eventHandling.h: -------------------------------------------------------------------------------- 1 | #ifndef EVENTHANDLING_H 2 | #define EVENTHANDLING_H 3 | 4 | #include 5 | #include "tinyfiledialogs.h" 6 | #include "imageloader.h" 7 | #include "ball.h" 8 | extern ball* Balls; 9 | 10 | extern int speed; 11 | extern int prev_speed; 12 | extern int ball_selected; 13 | extern int same_size; 14 | extern int same_color; 15 | extern int winw; 16 | extern int winh; 17 | extern int last_x; 18 | extern int last_y; 19 | extern float rotationX; 20 | extern float rotationY; 21 | extern float scale; 22 | extern bool playing; 23 | extern float view_rotate[]; 24 | extern float obj_pos[]; 25 | 26 | 27 | #define ENABLE_ID 300 28 | #define DISABLE_ID 301 29 | 30 | enum theme {Default, BlackMetallic, HighContrast, Pool }; 31 | extern theme thm; 32 | extern int thm_int; 33 | 34 | 35 | /** Pointers to the windows and some of the controls we'll create **/ 36 | extern GLUI *glui, *glui2; 37 | extern GLUI_RadioGroup *radio2D, *radio3D, *radio; 38 | extern GLUI_Panel *obj_panel; 39 | extern GLUI_Scrollbar* sbRed, *sbGreen, *sbBlue; 40 | extern GLUI_Button *gb, *gb_enable, *gb_disable; 41 | 42 | struct Mouse 43 | { 44 | int x; /* the x coordinate of the mouse cursor */ 45 | int y; /* the y coordinate of the mouse cursor */ 46 | int lmb; /* is the left button pressed? */ 47 | int mmb; /* is the middle button pressed? */ 48 | int rmb; /* is the right button pressed? */ 49 | int xpress; /* stores the x-coord of when the first button press occurred */ 50 | int ypress; /* stores the y-coord of when the first button press occurred */ 51 | }; 52 | 53 | extern Mouse TheMouse; 54 | 55 | bool ClickTest(ball* b,int x,int y); 56 | void MouseButton(int button,int state,int x, int y); 57 | void MouseMotion(int x, int y); 58 | void MousePassiveMotion(int x, int y); 59 | void handleKeypress(unsigned char key,int x, int y); 60 | void handleSpecialKeypress(int key,int x, int y); 61 | 62 | void cbForPP(int value); 63 | void cbIncSpeed(); 64 | void cbIncBallSpeed(int bid); 65 | void cbDecSpeed(); 66 | void cbDecBallSpeed(int bid); 67 | void cbDefault(); 68 | void cbHighContrast(); 69 | void cbBlackMetallic(); 70 | void cbPool(); 71 | void cbTheme(int value); 72 | void cbSameSize(int value); 73 | void cbSameColor(int value); 74 | void cbMode(int value); 75 | void cbColorChooser(int value); 76 | void cbSpeed(int value); 77 | void control_cb(int control); 78 | 79 | #endif 80 | -------------------------------------------------------------------------------- /src/gui.cpp: -------------------------------------------------------------------------------- 1 | #include "ball.h" 2 | #include "eventHandling.h" 3 | #include "gui.h" 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | float _time = 25; 9 | float ratio; 10 | int main_window; 11 | GLuint bg_id; //The id of the texture 12 | 13 | #define ENABLE_ID 300 14 | #define DISABLE_ID 301 15 | 16 | /********************************************************Lighting Attributes*******************************************/ 17 | GLfloat light_ambient[] = { 0.1f, 0.1f, 0.3f, 1.0f }; 18 | GLfloat light_diffuse[] = { .6f, .6f, 0.6f, 1.0f }; 19 | GLfloat light_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f }; 20 | GLfloat light_position[] = { .5f, .5f, 1.0f, 0.0f }; 21 | 22 | GLfloat mat_ambient[] = { 0.7f, 0.7f, 0.7f, 1.0f }; 23 | GLfloat mat_diffuse[] = { 0.8f, 0.8f, 0.8f, 1.0f }; 24 | GLfloat mat_specular[] = { 1.0f, 1.0f, 1.0f, 1.0f }; 25 | GLfloat high_shininess[] = { 100.0f }; 26 | 27 | GLfloat lights_rotation[16] = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1 }; 28 | 29 | 30 | /*******************************************************Load BackGroung Image*********************************************/ 31 | GLuint loadTexture(Image* image) { 32 | GLuint textureId; 33 | glGenTextures(1, &textureId); //Make room for our texture 34 | glBindTexture(GL_TEXTURE_2D, textureId); //Tell OpenGL which texture to edit 35 | //Map the image to the texture 36 | glTexImage2D(GL_TEXTURE_2D, //Always GL_TEXTURE_2D 37 | 0, //0 for now 38 | GL_RGB, //Format OpenGL uses for image 39 | image->width, image->height, //Width and height 40 | 0, //The border of the image 41 | GL_RGB, //GL_RGB, because pixels are stored in RGB format 42 | GL_UNSIGNED_BYTE, //GL_UNSIGNED_BYTE, because pixels are stored 43 | //as unsigned numbers 44 | image->pixels); //The actual pixel data 45 | return textureId; //Returns the id of the texture 46 | } 47 | 48 | //Initializes 3D rendering 49 | void initRendering() { 50 | //Makes 3D drawing work when something is in front of something else 51 | glEnable(GL_DEPTH_TEST); 52 | glEnable(GL_BLEND); //Enable alpha blending 53 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); //Set the blend function 54 | Image* bg_image = loadBMP("bg.bmp"); 55 | bg_id = loadTexture(bg_image); 56 | delete bg_image; 57 | } 58 | 59 | //Called when the window is resized 60 | void handleResize(int w, int h) { 61 | //Tell OpenGL how to convert from coordinates to pixel values 62 | 63 | int tx, ty, tw, th; 64 | GLUI_Master.get_viewport_area( &tx, &ty, &tw, &th ); 65 | glViewport( tx, ty, tw, th ); 66 | 67 | winw = w; 68 | winh = h; 69 | ratio = (float)w / (float)h; 70 | wlx = wly * ratio; 71 | } 72 | 73 | // function for regularly updating the scene 74 | void update(int value){ 75 | if (playing){ 76 | if (threeD){ 77 | for (int i=0;i 26 | #include 27 | 28 | #include "imageloader.h" 29 | 30 | using namespace std; 31 | 32 | Image::Image(char* ps, int w, int h) : pixels(ps), width(w), height(h) { 33 | 34 | } 35 | 36 | Image::~Image() { 37 | delete[] pixels; 38 | } 39 | 40 | namespace { 41 | //Converts a four-character array to an integer, using little-endian form 42 | int toInt(const char* bytes) { 43 | return (int)(((unsigned char)bytes[3] << 24) | 44 | ((unsigned char)bytes[2] << 16) | 45 | ((unsigned char)bytes[1] << 8) | 46 | (unsigned char)bytes[0]); 47 | } 48 | 49 | //Converts a two-character array to a short, using little-endian form 50 | short toShort(const char* bytes) { 51 | return (short)(((unsigned char)bytes[1] << 8) | 52 | (unsigned char)bytes[0]); 53 | } 54 | 55 | //Reads the next four bytes as an integer, using little-endian form 56 | int readInt(ifstream &input) { 57 | char buffer[4]; 58 | input.read(buffer, 4); 59 | return toInt(buffer); 60 | } 61 | 62 | //Reads the next two bytes as a short, using little-endian form 63 | short readShort(ifstream &input) { 64 | char buffer[2]; 65 | input.read(buffer, 2); 66 | return toShort(buffer); 67 | } 68 | 69 | //Just like auto_ptr, but for arrays 70 | template 71 | class auto_array { 72 | private: 73 | T* array; 74 | mutable bool isReleased; 75 | public: 76 | explicit auto_array(T* array_ = NULL) : 77 | array(array_), isReleased(false) { 78 | } 79 | 80 | auto_array(const auto_array &aarray) { 81 | array = aarray.array; 82 | isReleased = aarray.isReleased; 83 | aarray.isReleased = true; 84 | } 85 | 86 | ~auto_array() { 87 | if (!isReleased && array != NULL) { 88 | delete[] array; 89 | } 90 | } 91 | 92 | T* get() const { 93 | return array; 94 | } 95 | 96 | T &operator*() const { 97 | return *array; 98 | } 99 | 100 | void operator=(const auto_array &aarray) { 101 | if (!isReleased && array != NULL) { 102 | delete[] array; 103 | } 104 | array = aarray.array; 105 | isReleased = aarray.isReleased; 106 | aarray.isReleased = true; 107 | } 108 | 109 | T* operator->() const { 110 | return array; 111 | } 112 | 113 | T* release() { 114 | isReleased = true; 115 | return array; 116 | } 117 | 118 | void reset(T* array_ = NULL) { 119 | if (!isReleased && array != NULL) { 120 | delete[] array; 121 | } 122 | array = array_; 123 | } 124 | 125 | T* operator+(int i) { 126 | return array + i; 127 | } 128 | 129 | T &operator[](int i) { 130 | return array[i]; 131 | } 132 | }; 133 | } 134 | 135 | Image* loadBMP(const char* filename) { 136 | ifstream input; 137 | input.open(filename, ifstream::binary); 138 | assert(!input.fail() || !"Could not find file"); 139 | char buffer[2]; 140 | input.read(buffer, 2); 141 | assert((buffer[0] == 'B' && buffer[1] == 'M') || (!"Not a bitmap file")); 142 | input.ignore(8); 143 | int dataOffset = readInt(input); 144 | 145 | //Read the header 146 | int headerSize = readInt(input); 147 | int width; 148 | int height; 149 | switch(headerSize) { 150 | case 40: 151 | //V3 152 | width = readInt(input); 153 | height = readInt(input); 154 | input.ignore(2); 155 | assert(readShort(input) == 24 || !"Image is not 24 bits per pixel"); 156 | assert(readShort(input) == 0 || !"Image is compressed"); 157 | break; 158 | case 12: 159 | //OS/2 V1 160 | width = readShort(input); 161 | height = readShort(input); 162 | input.ignore(2); 163 | assert(readShort(input) == 24 || !"Image is not 24 bits per pixel"); 164 | break; 165 | case 64: 166 | //OS/2 V2 167 | assert(!"Can't load OS/2 V2 bitmaps"); 168 | break; 169 | case 108: 170 | //Windows V4 171 | assert(!"Can't load Windows V4 bitmaps"); 172 | break; 173 | case 124: 174 | //Windows V5 175 | assert(!"Can't load Windows V5 bitmaps"); 176 | break; 177 | default: 178 | assert(!"Unknown bitmap format"); 179 | } 180 | 181 | //Read the data 182 | int bytesPerRow = ((width * 3 + 3) / 4) * 4 - (width * 3 % 4); 183 | int size = bytesPerRow * height; 184 | auto_array pixels(new char[size]); 185 | input.seekg(dataOffset, ios_base::beg); 186 | input.read(pixels.get(), size); 187 | 188 | //Get the data into the right format 189 | auto_array pixels2(new char[width * height * 3]); 190 | for(int y = 0; y < height; y++) { 191 | for(int x = 0; x < width; x++) { 192 | for(int c = 0; c < 3; c++) { 193 | pixels2[3 * (width * y + x) + c] = 194 | pixels[bytesPerRow * y + 3 * x + (2 - c)]; 195 | } 196 | } 197 | } 198 | 199 | input.close(); 200 | return new Image(pixels2.release(), width, height); 201 | } 202 | 203 | -------------------------------------------------------------------------------- /src/imageloader.h: -------------------------------------------------------------------------------- 1 | /* Permission is hereby granted, free of charge, to any person obtaining a copy 2 | * of this software and associated documentation files (the "Software"), to deal 3 | * in the Software without restriction, including without limitation the rights 4 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 5 | * copies of the Software, and to permit persons to whom the Software is 6 | * furnished to do so, subject to the following conditions: 7 | * 8 | * The above notice and this permission notice shall be included in all copies 9 | * or substantial portions of the Software. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 12 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 13 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 14 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 15 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 16 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 17 | * SOFTWARE. 18 | */ 19 | /* File for "Textures" lesson of the OpenGL tutorial on 20 | * www.videotutorialsrock.com 21 | */ 22 | 23 | 24 | 25 | #ifndef IMAGE_LOADER_H_INCLUDED 26 | #define IMAGE_LOADER_H_INCLUDED 27 | 28 | //Represents an image 29 | class Image { 30 | public: 31 | Image(char* ps, int w, int h); 32 | ~Image(); 33 | 34 | /* An array of the form (R1, G1, B1, R2, G2, B2, ...) indicating the 35 | * color of each pixel in image. Color components range from 0 to 255. 36 | * The array starts the bottom-left pixel, then moves right to the end 37 | * of the row, then moves up to the next column, and so on. This is the 38 | * format in which OpenGL likes images. 39 | */ 40 | char* pixels; 41 | int width; 42 | int height; 43 | }; 44 | 45 | //Reads a bitmap image from file. 46 | Image* loadBMP(const char* filename); 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "ball.h" 2 | #include "eventHandling.h" 3 | #include "gui.h" 4 | #include "tinyfiledialogs.h" 5 | #include "imageloader.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | using namespace std; 11 | 12 | int main(int argc, char** argv) { 13 | srand(time(0)); 14 | 15 | glutInit(&argc, argv); //Initialize GLUT 16 | glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); 17 | glutInitWindowSize(winw,winh); //Set the window size 18 | glutInitWindowPosition((glutGet(GLUT_SCREEN_WIDTH)-winw)/2,(glutGet(GLUT_SCREEN_HEIGHT)-winh)/2); 19 | 20 | main_window = glutCreateWindow("Bouncing Balls Animation"); //Create the window 21 | initRendering(); //Initialize rendering 22 | makeBalls(); //initialize ball attributes 23 | 24 | //Set handler functions for drawing, keypresses, and window resizes 25 | glutDisplayFunc(drawScene); 26 | glutTimerFunc(_time, update, 0); 27 | 28 | GLUI_Master.set_glutKeyboardFunc(handleKeypress); 29 | GLUI_Master.set_glutSpecialFunc(handleSpecialKeypress); 30 | GLUI_Master.set_glutMouseFunc(MouseButton); 31 | glutMotionFunc(MouseMotion); 32 | glutPassiveMotionFunc(MousePassiveMotion); 33 | GLUI_Master.set_glutReshapeFunc(handleResize); 34 | 35 | ///********************************************************** GLUI******************************************************/// 36 | 37 | glui = GLUI_Master.create_glui_subwindow( main_window, GLUI_SUBWINDOW_RIGHT); 38 | 39 | obj_panel = new GLUI_Panel(glui, "Ball Properties" ); 40 | 41 | new GLUI_StaticText(obj_panel,""); 42 | new GLUI_Button( obj_panel, "Play/Pause" , 0, cbForPP ); 43 | new GLUI_Separator( obj_panel ); 44 | 45 | (new GLUI_Spinner( obj_panel, "Number of Balls:", &num_balls)) 46 | ->set_int_limits( 1,max_balls ); 47 | 48 | new GLUI_StaticText( obj_panel, "Change Speed of Balls : " ); 49 | (new GLUI_Scrollbar( obj_panel, "Speed",GLUI_SCROLL_HORIZONTAL, &speed,1,cbSpeed)) 50 | ->set_int_limits( 1,10 ); 51 | new GLUI_StaticText( obj_panel, "" ); 52 | 53 | new GLUI_Checkbox( obj_panel, "Same Size of Balls", &same_size,1 ,cbSameSize); 54 | new GLUI_StaticText( obj_panel, "" ); 55 | 56 | new GLUI_Checkbox( obj_panel, "Change Color of Balls", &same_color, 1, cbSameColor ); 57 | new GLUI_StaticText( obj_panel, "" ); 58 | 59 | char choose_color[] = "Choose Color"; 60 | gb = new GLUI_Button( obj_panel, choose_color, 0,(GLUI_Update_CB)cbColorChooser ); 61 | gb->disable(); 62 | 63 | new GLUI_Separator( obj_panel ); 64 | new GLUI_StaticText( obj_panel, "Red, Green, Blue :" ); 65 | sbRed = new GLUI_Scrollbar( obj_panel, "Red", GLUI_SCROLL_HORIZONTAL, &globalR,1,cbSameColor); 66 | sbRed->set_float_limits(1,256); 67 | 68 | sbGreen = new GLUI_Scrollbar( obj_panel, "Green", GLUI_SCROLL_HORIZONTAL, &globalG,1,cbSameColor); 69 | sbGreen->set_float_limits(1,256); 70 | 71 | sbBlue = new GLUI_Scrollbar( obj_panel, "Blue", GLUI_SCROLL_HORIZONTAL, &globalB,1,cbSameColor); 72 | sbBlue->set_float_limits(1,256); 73 | 74 | sbRed->disable(); 75 | sbGreen->disable(); 76 | sbBlue->disable(); 77 | 78 | new GLUI_Separator( glui ); 79 | 80 | obj_panel = new GLUI_Rollout(glui, "Look And Feel", false ); 81 | 82 | radio2D = new GLUI_RadioGroup( obj_panel,&thm_int,1, cbTheme); 83 | new GLUI_RadioButton( radio2D, "Default" ); 84 | new GLUI_RadioButton( radio2D, "Black Metallic" ); 85 | new GLUI_RadioButton( radio2D, "High Contrast" ); 86 | new GLUI_RadioButton( radio2D, "Pool/Billiard" ); 87 | new GLUI_StaticText( obj_panel, "" ); 88 | 89 | radio3D = new GLUI_RadioGroup( obj_panel,&thm_int,1, cbTheme); 90 | new GLUI_RadioButton( radio3D, "Default" ); 91 | new GLUI_RadioButton( radio3D, "Black Metallic" ); 92 | new GLUI_RadioButton( radio3D, "High Contrast" ); 93 | new GLUI_RadioButton( radio3D, "Pool/Billiard" ); 94 | new GLUI_StaticText( obj_panel, "" ); 95 | radio3D->disable(); 96 | 97 | new GLUI_Separator( glui ); 98 | 99 | obj_panel = new GLUI_Panel(glui, "Mode"); 100 | radio = new GLUI_RadioGroup( obj_panel,&threeD,1, cbMode); 101 | new GLUI_RadioButton( radio, "2D" ); 102 | new GLUI_RadioButton( radio, "3D" ); 103 | 104 | new GLUI_Separator( glui ); 105 | gb_enable = new GLUI_Button( glui, "Disable movement", DISABLE_ID, control_cb ); 106 | gb_disable = new GLUI_Button( glui, "Enable movement", ENABLE_ID, control_cb ); 107 | 108 | gb_enable->disable(); 109 | gb_disable->disable(); 110 | 111 | new GLUI_StaticText( glui, "" ); 112 | 113 | new GLUI_Separator( glui ); 114 | new GLUI_Button( glui, "Quit", 0,(GLUI_Update_CB)exit ); 115 | 116 | glui2 = GLUI_Master.create_glui_subwindow( main_window, 117 | GLUI_SUBWINDOW_BOTTOM ); 118 | glui2->set_main_gfx_window( main_window ); 119 | 120 | GLUI_Translation *trans_xy = 121 | new GLUI_Translation(glui2, "Objects XY", GLUI_TRANSLATION_XY, obj_pos ); 122 | trans_xy->set_speed( .005 ); 123 | new GLUI_Column( glui2, false ); 124 | GLUI_Translation *trans_x = 125 | new GLUI_Translation(glui2, "Objects X", GLUI_TRANSLATION_X, obj_pos ); 126 | trans_x->set_speed( .005 ); 127 | new GLUI_Column( glui2, false ); 128 | GLUI_Translation *trans_y = 129 | new GLUI_Translation( glui2, "Objects Y", GLUI_TRANSLATION_Y, &obj_pos[1] ); 130 | trans_y->set_speed( .005 ); 131 | new GLUI_Column( glui2, false ); 132 | GLUI_Translation *trans_z = 133 | new GLUI_Translation( glui2, "Objects Z", GLUI_TRANSLATION_Z, &obj_pos[2] ); 134 | trans_z->set_speed( .005 ); 135 | 136 | glui->set_main_gfx_window(main_window); 137 | 138 | 139 | GLUI_Master.set_glutIdleFunc(myGlutIdle); 140 | 141 | ///******************************************************************************************************************/// 142 | 143 | glutMainLoop(); //Start the main loop. glutMainLoop doesn't return. 144 | return 0; //This line is never reached 145 | } 146 | -------------------------------------------------------------------------------- /src/makefile: -------------------------------------------------------------------------------- 1 | CXX = g++ 2 | CFLAGS = -std=c++11 -g 3 | LFLAGS = -lglui -lglut -lGLU -lGL -lm 4 | SRCS = main.cpp ball.cpp eventHandling.cpp gui.cpp imageloader.cpp tinyfiledialogs.c 5 | 6 | all: 7 | $(CXX) $(CFLAGS) -o a.out $(SRCS) $(LFLAGS) 8 | -------------------------------------------------------------------------------- /src/tinyfiledialogs.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include /* on old system try instead */ 7 | #include /* for freebsd */ 8 | 9 | #include "tinyfiledialogs.h" /* not needed */ 10 | 11 | #define MAX_PATH_OR_CMD 1024 /* _MAX_PATH or MAX_PATH */ 12 | #define MAX_MULTIPLE 32 13 | 14 | int tinyfd_forceConsole = 0 ; /* for UNIX only: 0 (default) or 1 */ 15 | /* 1 forces all dialogs into console mode even when the X server is present */ 16 | /* can be modified at run time */ 17 | 18 | static char * getPathWithoutFinalSlash( 19 | char * const aoDestination, /* make sure it is allocated, use _MAX_PATH */ 20 | char const * const aSource) /* aoDestination and aSource can be the same */ 21 | { 22 | char const * lTmp ; 23 | if ( aSource ) 24 | { 25 | lTmp = strrchr(aSource, (int)* "/"); 26 | if (!lTmp) 27 | { 28 | lTmp = strrchr(aSource, (int)* "\\"); 29 | } 30 | if (lTmp) 31 | { 32 | strncpy(aoDestination, aSource, lTmp - aSource); 33 | aoDestination[lTmp - aSource] = '\0'; 34 | } 35 | else 36 | { 37 | * aoDestination = '\0'; 38 | } 39 | } 40 | else 41 | { 42 | * aoDestination = '\0'; 43 | } 44 | return aoDestination; 45 | } 46 | 47 | 48 | static char * getLastName( 49 | char * const aoDestination, /* make sure it is allocated */ 50 | char const * const aSource) 51 | { 52 | /* copy the last name after '/' or '\' */ 53 | char const * lTmp ; 54 | if ( aSource ) 55 | { 56 | lTmp = strrchr(aSource, (int)* "/"); 57 | if (!lTmp) 58 | { 59 | lTmp = strrchr(aSource, (int)* "\\"); 60 | } 61 | if (lTmp) 62 | { 63 | strcpy(aoDestination, lTmp + 1); 64 | } 65 | else 66 | { 67 | strcpy(aoDestination, aSource); 68 | } 69 | } 70 | else 71 | { 72 | * aoDestination = '\0'; 73 | } 74 | return aoDestination; 75 | } 76 | 77 | 78 | static void Hex2RGB( char const aHexRGB [ 8 ] , 79 | unsigned char aoResultRGB [ 3 ] ) 80 | { 81 | char lColorChannel [ 8 ] ; 82 | strcpy(lColorChannel, aHexRGB ) ; 83 | aoResultRGB[2] = (unsigned char)strtoul(lColorChannel + 5, NULL, 16); 84 | lColorChannel[5] = '\0'; 85 | aoResultRGB[1] = (unsigned char)strtoul(lColorChannel + 3, NULL, 16); 86 | lColorChannel[3] = '\0'; 87 | aoResultRGB[0] = (unsigned char)strtoul(lColorChannel + 1, NULL, 16); 88 | /* printf("%d %d %d\n", aoResultRGB[0], aoResultRGB[1], aoResultRGB[2]);//*/ 89 | } 90 | 91 | static void RGB2Hex( unsigned char const aRGB [ 3 ] , 92 | char aoResultHexRGB [ 8 ] ) 93 | { 94 | sprintf(aoResultHexRGB, "#%02hhx%02hhx%02hhx", aRGB[0], aRGB[1], aRGB[2]); 95 | /* printf("aoResultHexRGB %s\n", aoResultHexRGB); //*/ 96 | } 97 | 98 | 99 | #ifdef _WIN32 100 | 101 | 102 | char const * tinyfd_saveFileDialog ( 103 | char const * const aTitle , /* NULL or "" */ 104 | char const * const aDefaultPathAndFile , /* NULL or "" */ 105 | int const aNumOfFileFilters , /* 0 */ 106 | char const * const * const aFileFilters ) /* NULL or {"*.jpg","*.png"} */ 107 | { 108 | static char lBuff [ MAX_PATH_OR_CMD ] ; 109 | char lDirname [ MAX_PATH_OR_CMD ] ; 110 | char lFileFilters[MAX_PATH_OR_CMD] = ""; 111 | char lString[MAX_PATH_OR_CMD] ; 112 | int i ; 113 | char * p; 114 | OPENFILENAME ofn ; 115 | 116 | getPathWithoutFinalSlash(lDirname, aDefaultPathAndFile); 117 | getLastName(lBuff, aDefaultPathAndFile); 118 | 119 | if (aNumOfFileFilters > 0) 120 | { 121 | strcat(lFileFilters, aFileFilters[0]); 122 | for (i = 1; i < aNumOfFileFilters; i++) 123 | { 124 | strcat(lFileFilters, ";"); 125 | strcat(lFileFilters, aFileFilters[i]); 126 | } 127 | strcat(lFileFilters, "\n"); 128 | strcpy(lString, lFileFilters); 129 | strcat(lFileFilters, lString); 130 | strcat(lFileFilters, "All Files\n*.*\n"); 131 | p = lFileFilters; 132 | while ((p = strchr(p, '\n')) != NULL) 133 | { 134 | *p = '\0'; 135 | p ++ ; 136 | } 137 | } 138 | 139 | ofn.lStructSize = sizeof(OPENFILENAME) ; 140 | ofn.hwndOwner = 0 ; 141 | ofn.hInstance = 0 ; 142 | ofn.lpstrFilter = lFileFilters ; 143 | ofn.lpstrCustomFilter = NULL ; 144 | ofn.nMaxCustFilter = 0 ; 145 | ofn.nFilterIndex = 0 ; 146 | ofn.lpstrFile = lBuff; 147 | ofn.nMaxFile = MAX_PATH_OR_CMD ; 148 | ofn.lpstrFileTitle = NULL ; 149 | ofn.nMaxFileTitle = _MAX_FNAME + _MAX_EXT ; 150 | ofn.lpstrInitialDir = lDirname; 151 | ofn.lpstrTitle = aTitle ; 152 | ofn.Flags = OFN_OVERWRITEPROMPT ; 153 | ofn.nFileOffset = 0 ; 154 | ofn.nFileExtension = 0 ; 155 | ofn.lpstrDefExt = NULL ; 156 | ofn.lCustData = 0L ; 157 | ofn.lpfnHook = NULL ; 158 | ofn.lpTemplateName = NULL ; 159 | 160 | if ( GetSaveFileName ( & ofn ) == 0 ) 161 | { 162 | return NULL ; 163 | } 164 | else 165 | { 166 | return lBuff ; 167 | } 168 | } 169 | 170 | 171 | /* in case of multiple files, the separator is | */ 172 | char const * tinyfd_openFileDialog ( 173 | char const * const aTitle , /* NULL or "" */ 174 | char const * const aDefaultPathAndFile , /* NULL or "" */ 175 | int const aNumOfFileFilters , /* 0 */ 176 | char const * const * const aFileFilters , /* NULL or {"*.jpg","*.png"} */ 177 | int aAllowMultipleSelects ) /* 0 or 1 */ 178 | { 179 | static char lBuff[MAX_MULTIPLE * MAX_PATH_OR_CMD]; 180 | char lDirname [ MAX_PATH_OR_CMD ] ; 181 | char lFileFilters[MAX_PATH_OR_CMD] = ""; 182 | char lTempString[MAX_PATH_OR_CMD] ; 183 | char * lPointers[MAX_MULTIPLE]; 184 | size_t lLengths[MAX_MULTIPLE]; 185 | int i , j ; 186 | char * p; 187 | OPENFILENAME ofn; 188 | size_t lBuffLen ; 189 | 190 | getPathWithoutFinalSlash(lDirname, aDefaultPathAndFile); 191 | getLastName(lBuff, aDefaultPathAndFile); 192 | 193 | if (aNumOfFileFilters > 0) 194 | { 195 | strcat(lFileFilters, aFileFilters[0]); 196 | for (i = 1; i < aNumOfFileFilters; i++) 197 | { 198 | strcat(lFileFilters, ";"); 199 | strcat(lFileFilters, aFileFilters[i]); 200 | } 201 | strcat(lFileFilters, "\n"); 202 | strcpy(lTempString, lFileFilters); 203 | strcat(lFileFilters, lTempString); 204 | strcat(lFileFilters, "All Files\n*.*\n"); 205 | p = lFileFilters; 206 | while ((p = strchr(p, '\n')) != NULL) 207 | { 208 | *p = '\0'; 209 | p ++ ; 210 | } 211 | } 212 | 213 | ofn.lStructSize = sizeof ( OPENFILENAME ) ; 214 | ofn.hwndOwner = 0 ; 215 | ofn.hInstance = 0 ; 216 | ofn.lpstrFilter = lFileFilters; 217 | ofn.lpstrCustomFilter = NULL ; 218 | ofn.nMaxCustFilter = 0 ; 219 | ofn.nFilterIndex = 0 ; 220 | ofn.lpstrFile = lBuff ; 221 | ofn.nMaxFile = MAX_PATH_OR_CMD ; 222 | ofn.lpstrFileTitle = NULL ; 223 | ofn.nMaxFileTitle = _MAX_FNAME + _MAX_EXT ; 224 | ofn.lpstrInitialDir = lDirname ; 225 | ofn.lpstrTitle = aTitle ; 226 | ofn.Flags = OFN_EXPLORER ; 227 | ofn.nFileOffset = 0 ; 228 | ofn.nFileExtension = 0 ; 229 | ofn.lpstrDefExt = NULL ; 230 | ofn.lCustData = 0L ; 231 | ofn.lpfnHook = NULL ; 232 | ofn.lpTemplateName = NULL ; 233 | 234 | if ( aAllowMultipleSelects ) 235 | { 236 | ofn.Flags |= OFN_ALLOWMULTISELECT; 237 | } 238 | 239 | if ( GetOpenFileName ( & ofn ) == 0 ) 240 | { 241 | return NULL ; 242 | } 243 | else 244 | { 245 | lBuffLen = strlen(lBuff) ; 246 | lPointers[0] = lBuff + lBuffLen + 1 ; 247 | if ( !aAllowMultipleSelects || (lPointers[0][0] == '\0') ) 248 | return lBuff ; 249 | 250 | i = 0 ; 251 | do 252 | { 253 | lLengths[i] = strlen(lPointers[i]); 254 | lPointers[i+1] = lPointers[i] + lLengths[i] + 1 ; 255 | i ++ ; 256 | } 257 | while ( lPointers[i][0] != '\0' ); 258 | i--; 259 | p = lBuff + sizeof(lBuff) - 1 ; 260 | * p = '\0'; 261 | for ( j = i ; j >=0 ; j-- ) 262 | { 263 | p -= lLengths[j]; 264 | memcpy(p, lPointers[j], lLengths[j]); 265 | p--; 266 | *p = '\\'; 267 | p -= lBuffLen ; 268 | memcpy(p, lBuff, lBuffLen); 269 | p--; 270 | *p = '|'; 271 | } 272 | p++; 273 | return p ; 274 | } 275 | } 276 | 277 | 278 | char const * tinyfd_selectFolderDialog ( 279 | char const * const aTitle , /* NULL or "" */ 280 | char const * const aDefaultPath ) /* NULL or "" */ 281 | { 282 | static char lBuff [ MAX_PATH_OR_CMD ] ; 283 | BROWSEINFO bInfo ; 284 | LPITEMIDLIST lpItem ; 285 | 286 | /* we can't use aDefaultPath */ 287 | bInfo.hwndOwner = 0 ; 288 | bInfo.pidlRoot = NULL ; 289 | bInfo.pszDisplayName = lBuff ; 290 | bInfo.lpszTitle = aTitle ; 291 | bInfo.ulFlags = 0 ; 292 | bInfo.lpfn = NULL ; 293 | bInfo.lParam = 0 ; 294 | bInfo.iImage = -1 ; 295 | 296 | lpItem = SHBrowseForFolder ( & bInfo ) ; 297 | if ( lpItem ) 298 | { 299 | SHGetPathFromIDList ( lpItem , lBuff ) ; 300 | } 301 | 302 | return lBuff ; 303 | } 304 | 305 | 306 | /* returns 0 for cancel/no , 1 for ok/yes */ 307 | int tinyfd_messageDialog ( 308 | char const * const aTitle , /* NULL or "" */ 309 | char const * const aMessage , /* NULL or "" */ /* may contain \n and \t */ 310 | char const * const aDialogType , /* "ok" "okcancel" "yesno" */ 311 | char const * const aIconType , /* "info" "warning" "error" "question" */ 312 | int const aDefaultButton ) /* 0 for cancel/no , 1 for ok/yes */ 313 | { 314 | int lBoxReturnValue; 315 | UINT aCode ; 316 | 317 | if ( ! strcmp( "warning" , aIconType ) ) 318 | { 319 | aCode = MB_ICONWARNING ; 320 | } 321 | else if (!strcmp("error", aIconType)) 322 | { 323 | aCode = MB_ICONERROR ; 324 | } 325 | else if (!strcmp("question", aIconType)) 326 | { 327 | aCode = MB_ICONQUESTION ; 328 | } 329 | else 330 | { 331 | aCode = MB_ICONINFORMATION ; 332 | } 333 | 334 | if ( ! strcmp( "okcancel" , aDialogType ) ) 335 | { 336 | aCode += MB_OKCANCEL ; 337 | if ( ! aDefaultButton ) 338 | { 339 | aCode += MB_DEFBUTTON2 ; 340 | } 341 | } 342 | else if ( ! strcmp( "yesno" , aDialogType ) ) 343 | { 344 | aCode += MB_YESNO ; 345 | if ( ! aDefaultButton ) 346 | { 347 | aCode += MB_DEFBUTTON2 ; 348 | } 349 | } 350 | else 351 | { 352 | aCode += MB_OK ; 353 | } 354 | 355 | 356 | lBoxReturnValue = MessageBox(NULL, aMessage, aTitle, aCode); 357 | if ( ( ! strcmp("ok", aDialogType) ) 358 | || (lBoxReturnValue == IDOK) 359 | || (lBoxReturnValue == IDYES) ) 360 | { 361 | return 1 ; 362 | } 363 | else 364 | { 365 | return 0 ; 366 | } 367 | } 368 | 369 | 370 | /* returns the hexcolor as a string "#FF0000" */ 371 | /* aoResultRGB also contains the result */ 372 | /* aDefaultRGB is used only if aDefaultHexRGB is NULL */ 373 | /* aDefaultRGB and aoResultRGB can be the same array */ 374 | char const * tinyfd_colorChooser( 375 | char const * const aTitle, /* NULL or "" */ 376 | char const * const aDefaultHexRGB, /* NULL or "#FF0000"*/ 377 | unsigned char aDefaultRGB[3], /* { 0 , 255 , 255 } */ 378 | unsigned char aoResultRGB[3]) /* { 0 , 0 , 0 } */ 379 | { 380 | static CHOOSECOLOR cc; 381 | static COLORREF crCustColors[16]; 382 | static char lResultHexRGB[8]; 383 | int lRet; 384 | 385 | if ( aDefaultHexRGB ) 386 | { 387 | Hex2RGB(aDefaultHexRGB, aDefaultRGB); 388 | } 389 | 390 | /* we can't use aTitle */ 391 | cc.lStructSize = sizeof ( CHOOSECOLOR ) ; 392 | cc.hwndOwner = NULL ; 393 | cc.hInstance = NULL ; 394 | cc.rgbResult = RGB(aDefaultRGB[0], aDefaultRGB[1], aDefaultRGB[2]); 395 | cc.lpCustColors = crCustColors; 396 | cc.Flags = CC_RGBINIT | CC_FULLOPEN; 397 | cc.lCustData = 0; 398 | cc.lpfnHook = NULL; 399 | cc.lpTemplateName = NULL; 400 | 401 | lRet = ChooseColor(&cc); 402 | 403 | if ( ! lRet ) 404 | { 405 | return NULL; 406 | } 407 | 408 | aoResultRGB[0] = GetRValue(cc.rgbResult); 409 | aoResultRGB[1] = GetGValue(cc.rgbResult); 410 | aoResultRGB[2] = GetBValue(cc.rgbResult); 411 | 412 | RGB2Hex(aoResultRGB, lResultHexRGB); 413 | 414 | return lResultHexRGB; 415 | } 416 | 417 | 418 | #else /* unix */ 419 | 420 | static char gPython2Name[16]; 421 | 422 | static char gTitle[] = "missing software !" ; 423 | 424 | static char gMessage[] = "tiny file dialogs on UNIX needs\n\t\ 425 | zenity (version 3 for the color chooser)\nor\tkdialog\nor\t\ 426 | python 2 with tkinter\nor\tdialog (display in console)\n" ; 427 | 428 | static int graphicMode() 429 | { 430 | return ( ! tinyfd_forceConsole ) && getenv ( "DISPLAY" ) ; 431 | } 432 | 433 | 434 | static int detectPresence ( char const * const aExecutable ) 435 | { 436 | FILE * lIn ; 437 | char lBuff [ MAX_PATH_OR_CMD ] ; 438 | char lTestedString [ MAX_PATH_OR_CMD ] = "which " ; 439 | strcat ( lTestedString , aExecutable ) ; 440 | lIn = popen ( lTestedString , "r" ) ; 441 | if ( ( fgets ( lBuff , sizeof ( lBuff ) , lIn ) != NULL ) 442 | && ( ! strchr ( lBuff , ':' ) ) ) 443 | { /* present */ 444 | pclose ( lIn ) ; 445 | return 1 ; 446 | } 447 | else 448 | { 449 | pclose ( lIn ) ; 450 | return 0 ; 451 | } 452 | } 453 | 454 | 455 | static int tryCommand ( char const * const aCommand ) 456 | { 457 | char lBuff [ MAX_PATH_OR_CMD ] ; 458 | FILE * lIn = popen ( aCommand , "r" ) ; 459 | if ( fgets ( lBuff , sizeof ( lBuff ) , lIn ) == NULL ) 460 | { /* present */ 461 | pclose ( lIn ) ; 462 | return 1 ; 463 | } 464 | else 465 | { 466 | pclose ( lIn ) ; 467 | return 0 ; 468 | } 469 | } 470 | 471 | 472 | static int whiptailPresent ( ) 473 | { 474 | static int lWhiptailPresent = -1 ; 475 | if ( lWhiptailPresent < 0 ) 476 | { 477 | lWhiptailPresent = detectPresence ( "whiptail" ) ; 478 | } 479 | return lWhiptailPresent && isatty ( 1 ) ; /* verify console presence */ 480 | } 481 | 482 | 483 | static int dialogPresent ( ) 484 | { 485 | static int lDialogPresent = -1 ; 486 | if ( lDialogPresent < 0 ) 487 | { 488 | lDialogPresent = detectPresence ( "dialog" ) ; 489 | } 490 | return lDialogPresent && isatty ( 1 ) ; /* verify console presence */ 491 | } 492 | 493 | 494 | static int xmessagePresent ( ) 495 | { 496 | static int lXmessagePresent = -1 ; 497 | if ( lXmessagePresent < 0 ) 498 | { 499 | lXmessagePresent = detectPresence("xmessage") ; 500 | } 501 | return lXmessagePresent && graphicMode ( ) ; 502 | } 503 | 504 | 505 | static int notifysendPresent ( ) 506 | { 507 | static int lNotifysendPresent = -1 ; 508 | if ( lNotifysendPresent < 0 ) 509 | { 510 | lNotifysendPresent = detectPresence("notify-send") ; 511 | } 512 | return lNotifysendPresent && graphicMode ( ) ; 513 | } 514 | 515 | 516 | static int osascriptPresent ( ) 517 | { 518 | static int lOsascriptPresent = -1 ; 519 | if ( lOsascriptPresent < 0 ) 520 | { 521 | lOsascriptPresent = detectPresence ( "osascript" ) ; 522 | } 523 | return lOsascriptPresent && graphicMode ( ) ; 524 | } 525 | 526 | 527 | static int kdialogPresent ( ) 528 | { 529 | static int lKdialogPresent = -1 ; 530 | if ( lKdialogPresent < 0 ) 531 | { 532 | lKdialogPresent = detectPresence("kdialog") ; 533 | } 534 | return lKdialogPresent && graphicMode ( ) ; 535 | } 536 | 537 | 538 | static int zenityPresent ( ) 539 | { 540 | static int lZenityPresent = -1 ; 541 | if ( lZenityPresent < 0 ) 542 | { 543 | lZenityPresent = detectPresence("zenity") ; 544 | } 545 | return lZenityPresent && graphicMode ( ) ; 546 | } 547 | 548 | 549 | static int zenity3Present ( ) 550 | { 551 | static int lZenity3Present = -1 ; 552 | char lBuff [ MAX_PATH_OR_CMD ] ; 553 | FILE * lIn ; 554 | 555 | if ( lZenity3Present < 0 ) 556 | { 557 | if ( ! zenityPresent() ) 558 | { 559 | lZenity3Present = 0 ; 560 | } 561 | else 562 | { 563 | lIn = popen ( "zenity --version" , "r" ) ; 564 | if ( ( fgets ( lBuff , sizeof ( lBuff ) , lIn ) != NULL ) 565 | && ( atoi(lBuff) >= 3 ) 566 | && ( atoi(strtok(lBuff,".")+1) >= 0 ) ) 567 | { 568 | lZenity3Present = 1 ; 569 | } 570 | else 571 | { 572 | lZenity3Present = 0 ; 573 | } 574 | pclose ( lIn ) ; 575 | } 576 | } 577 | return lZenity3Present && graphicMode ( ) ; 578 | } 579 | 580 | 581 | static int tkinter2Present ( ) 582 | { 583 | static int lTkinter2Present = -1 ; 584 | char lPythonCommand[256]; 585 | char lPythonParams[256] = 586 | "-c \"try:\n\timport Tkinter;\nexcept:\n\tprint(0);\""; 587 | int i; 588 | 589 | if ( lTkinter2Present < 0 ) 590 | { 591 | strcpy(gPython2Name , "python" ) ; 592 | sprintf ( lPythonCommand , "%s %s" , gPython2Name , lPythonParams ) ; 593 | lTkinter2Present = tryCommand(lPythonCommand); 594 | if ( ! lTkinter2Present ) 595 | { 596 | strcpy(gPython2Name , "python2" ) ; 597 | if ( detectPresence(gPython2Name) ) 598 | { 599 | sprintf ( lPythonCommand , "%s %s" , gPython2Name , lPythonParams ) ; 600 | lTkinter2Present = tryCommand(lPythonCommand); 601 | } 602 | else 603 | { 604 | for ( i = 9 ; i >= 0 ; i -- ) 605 | { 606 | sprintf ( gPython2Name , "python2.%d" , i ) ; 607 | if ( detectPresence(gPython2Name) ) 608 | { 609 | sprintf ( lPythonCommand , "%s %s" , gPython2Name , lPythonParams ) ; 610 | lTkinter2Present = tryCommand(lPythonCommand); 611 | break ; 612 | } 613 | } 614 | } 615 | } 616 | } 617 | /* printf ("gPython2Name %s\n", gPython2Name) ; //*/ 618 | return lTkinter2Present && graphicMode ( ) ; 619 | } 620 | 621 | 622 | static void replaceSubStr ( char const * const aSource , 623 | char const * const aOldSubStr , 624 | char const * const aNewSubStr , 625 | char * const aoDestination ) 626 | { 627 | char const * pOccurence ; 628 | char const * p ; 629 | char const * lNewSubStr = "" ; 630 | 631 | if ( ! aSource ) 632 | { 633 | * aoDestination = '\0' ; 634 | return ; 635 | } 636 | if ( ! aOldSubStr ) 637 | { 638 | strcpy ( aoDestination , aSource ) ; 639 | return ; 640 | } 641 | if ( aNewSubStr ) 642 | { 643 | lNewSubStr = aNewSubStr ; 644 | } 645 | p = aSource ; 646 | int lOldSubLen = strlen ( aOldSubStr ) ; 647 | * aoDestination = '\0' ; 648 | while ( ( pOccurence = strstr ( p , aOldSubStr ) ) != NULL ) 649 | { 650 | strncat ( aoDestination , p , pOccurence - p ) ; 651 | strcat ( aoDestination , lNewSubStr ) ; 652 | p = pOccurence + lOldSubLen ; 653 | } 654 | strcat ( aoDestination , p ) ; 655 | } 656 | 657 | 658 | char const * tinyfd_saveFileDialog ( 659 | char const * const aTitle , /* NULL or "" */ 660 | char const * const aDefaultPathAndFile , /* NULL or "" */ 661 | int const aNumOfFileFilters , /* 0 */ 662 | char const * const * const aFileFilters ) /* NULL or {"*.jpg","*.png"} */ 663 | { 664 | static char lBuff [ MAX_PATH_OR_CMD ] ; 665 | char lDialogString [ MAX_PATH_OR_CMD ] ; 666 | char lString [ MAX_PATH_OR_CMD ] ; 667 | int i ; 668 | DIR * lDir ; 669 | FILE * lIn ; 670 | lBuff[0]='\0'; 671 | 672 | if ( zenityPresent() ) 673 | { 674 | strcpy ( lDialogString , 675 | "zenity --file-selection --save --confirm-overwrite" ) ; 676 | if ( aTitle && strlen(aTitle) ) 677 | { 678 | strcat(lDialogString, " --title=\"") ; 679 | strcat(lDialogString, aTitle) ; 680 | strcat(lDialogString, "\"") ; 681 | } 682 | if ( aDefaultPathAndFile && strlen(aDefaultPathAndFile) ) 683 | { 684 | strcat(lDialogString, " --filename=\"") ; 685 | strcat(lDialogString, aDefaultPathAndFile) ; 686 | strcat(lDialogString, "\"") ; 687 | } 688 | if ( aNumOfFileFilters > 0 ) 689 | { 690 | strcat ( lDialogString , " --file-filter='" ) ; 691 | for ( i = 0 ; i < aNumOfFileFilters ; i ++ ) 692 | { 693 | strcat ( lDialogString , aFileFilters [ i ] ) ; 694 | strcat ( lDialogString , " " ) ; 695 | } 696 | strcat ( lDialogString , "' --file-filter='All files | *'" ) ; 697 | } 698 | } 699 | else if ( kdialogPresent() ) 700 | { 701 | strcpy ( lDialogString , "kdialog --getsavefilename" ) ; 702 | if ( aDefaultPathAndFile && strlen(aDefaultPathAndFile) ) 703 | { 704 | strcat(lDialogString, " \"") ; 705 | strcat(lDialogString, aDefaultPathAndFile ) ; 706 | strcat(lDialogString , "\"" ) ; 707 | } 708 | else 709 | { 710 | strcat(lDialogString, " :" ) ; 711 | } 712 | if ( aNumOfFileFilters > 0 ) 713 | { 714 | strcat(lDialogString , " \"" ) ; 715 | for ( i = 0 ; i < aNumOfFileFilters ; i ++ ) 716 | { 717 | strcat ( lDialogString , aFileFilters [ i ] ) ; 718 | strcat ( lDialogString , " " ) ; 719 | } 720 | strcat ( lDialogString , "\"" ) ; 721 | } 722 | if ( aTitle && strlen(aTitle) ) 723 | { 724 | strcat(lDialogString, " --title \"") ; 725 | strcat(lDialogString, aTitle) ; 726 | strcat(lDialogString, "\"") ; 727 | } 728 | } 729 | else if ( osascriptPresent ( ) ) 730 | { 731 | strcpy ( lDialogString , "osascript -e 'POSIX path of ( choose file name " ); 732 | if ( aTitle && strlen(aTitle) ) 733 | { 734 | strcat(lDialogString, "with prompt \"") ; 735 | strcat(lDialogString, aTitle) ; 736 | strcat(lDialogString, "\" ") ; 737 | } 738 | getPathWithoutFinalSlash ( lString , aDefaultPathAndFile ) ; 739 | if ( strlen(lString) ) 740 | { 741 | strcat(lDialogString, "default location \"") ; 742 | strcat(lDialogString, lString ) ; 743 | strcat(lDialogString , "\" " ) ; 744 | } 745 | getLastName ( lString , aDefaultPathAndFile ) ; 746 | if ( strlen(lString) ) 747 | { 748 | strcat(lDialogString, "default name \"") ; 749 | strcat(lDialogString, lString ) ; 750 | strcat(lDialogString , "\" " ) ; 751 | } 752 | strcat ( lDialogString , ")'" ) ; 753 | } 754 | else if ( tkinter2Present ( ) ) 755 | { 756 | strcpy ( lDialogString , gPython2Name ) ; 757 | if ( ! isatty ( 1 ) ) 758 | { 759 | strcat ( lDialogString , " -i" ) ; /* for osx without console */ 760 | } 761 | strcat ( lDialogString , 762 | " -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();"); 763 | 764 | if ( osascriptPresent ( ) ) 765 | { 766 | strcat ( lDialogString , 767 | "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ 768 | frontmost of process \\\"Python\\\" to true' ''');"); 769 | } 770 | 771 | strcat ( lDialogString , "print tkFileDialog.asksaveasfilename("); 772 | if ( aTitle && strlen(aTitle) ) 773 | { 774 | strcat(lDialogString, "title='") ; 775 | strcat(lDialogString, aTitle) ; 776 | strcat(lDialogString, "',") ; 777 | } 778 | if ( aDefaultPathAndFile && strlen(aDefaultPathAndFile) ) 779 | { 780 | getPathWithoutFinalSlash ( lString , aDefaultPathAndFile ) ; 781 | if ( strlen(lString) ) 782 | { 783 | strcat(lDialogString, "initialdir='") ; 784 | strcat(lDialogString, lString ) ; 785 | strcat(lDialogString , "'," ) ; 786 | } 787 | getLastName ( lString , aDefaultPathAndFile ) ; 788 | if ( strlen(lString) ) 789 | { 790 | strcat(lDialogString, "initialfile='") ; 791 | strcat(lDialogString, lString ) ; 792 | strcat(lDialogString , "'," ) ; 793 | } 794 | } 795 | if ( ( aNumOfFileFilters > 1 ) 796 | || ( ( aNumOfFileFilters == 1 ) /* test because poor osx behaviour */ 797 | && ( aFileFilters[0][strlen(aFileFilters[0])-1] != '*' ) ) ) 798 | { 799 | strcat(lDialogString , "filetypes=(" ) ; 800 | for ( i = 0 ; i < aNumOfFileFilters ; i ++ ) 801 | { 802 | strcat ( lDialogString , "('','" ) ; 803 | strcat ( lDialogString , aFileFilters [ i ] ) ; 804 | strcat ( lDialogString , "')," ) ; 805 | } 806 | strcat ( lDialogString , "('All files','*'))" ) ; 807 | } 808 | strcat ( lDialogString , ")\"" ) ; 809 | } 810 | else if ( dialogPresent ( ) ) 811 | { 812 | strcpy ( lDialogString , "(dialog " ) ; 813 | if ( aTitle && strlen(aTitle) ) 814 | { 815 | strcat(lDialogString, "--title \"") ; 816 | strcat(lDialogString, aTitle) ; 817 | strcat(lDialogString, "\" ") ; 818 | } 819 | strcat ( lDialogString , "--fselect \"" ) ; 820 | if ( aDefaultPathAndFile && strlen(aDefaultPathAndFile) ) 821 | { 822 | strcat(lDialogString, aDefaultPathAndFile) ; 823 | } 824 | strcat(lDialogString, "\" 0 0 >/dev/tty) 2>&1 ; echo >/dev/tty") ; 825 | } 826 | else 827 | { 828 | tinyfd_messageDialog (gTitle, gMessage,"ok","error",1 ) ; 829 | return NULL ; 830 | } 831 | /* printf ( "lDialogString: %s\n" , lDialogString ) ; //*/ 832 | if ( ! ( lIn = popen ( lDialogString , "r" ) ) ) 833 | { 834 | return NULL ; 835 | } 836 | while ( fgets ( lBuff , sizeof ( lBuff ) , lIn ) != NULL ) 837 | {} 838 | pclose ( lIn ) ; 839 | if ( lBuff[ strlen ( lBuff ) -1 ] == '\n' ) 840 | { 841 | lBuff[ strlen ( lBuff ) -1 ] = '\0' ; 842 | } 843 | /* printf ( "lBuff: %s\n" , lBuff ) ; //*/ 844 | getPathWithoutFinalSlash ( lString , lBuff ) ; 845 | if ( strlen ( lString ) > 0 ) 846 | { 847 | lDir = opendir ( lString ) ; 848 | if ( ! lDir ) 849 | { 850 | return NULL ; 851 | } 852 | closedir ( lDir ) ; 853 | } 854 | return lBuff ; 855 | } 856 | 857 | 858 | /* in case of multiple files, the separator is | */ 859 | char const * tinyfd_openFileDialog ( 860 | char const * const aTitle , /* NULL or "" */ 861 | char const * const aDefaultPathAndFile , /* NULL or "" */ 862 | int const aNumOfFileFilters , /* 0 */ 863 | char const * const * const aFileFilters , /* NULL or {"*.jpg","*.png"} */ 864 | int aAllowMultipleSelects ) /* 0 or 1 */ 865 | { 866 | static char lBuff [ MAX_MULTIPLE * MAX_PATH_OR_CMD ] ; 867 | char lDialogString [ MAX_PATH_OR_CMD ] ; 868 | char lString [ MAX_PATH_OR_CMD ] ; 869 | int i ; 870 | FILE * lIn ; 871 | char * p ; 872 | int lWasKdialog = 0 ; 873 | lBuff[0]='\0'; 874 | 875 | if ( zenityPresent() ) 876 | { 877 | strcpy ( lDialogString ,"zenity --file-selection" ) ; 878 | if ( aAllowMultipleSelects ) 879 | { 880 | strcat ( lDialogString , " --multiple" ) ; 881 | } 882 | if ( aTitle && strlen(aTitle) ) 883 | { 884 | strcat(lDialogString, " --title=\"") ; 885 | strcat(lDialogString, aTitle) ; 886 | strcat(lDialogString, "\"") ; 887 | } 888 | if ( aDefaultPathAndFile && strlen(aDefaultPathAndFile) ) 889 | { 890 | strcat(lDialogString, " --filename=\"") ; 891 | strcat(lDialogString, aDefaultPathAndFile) ; 892 | strcat(lDialogString, "\"") ; 893 | } 894 | if ( aNumOfFileFilters > 0 ) 895 | { 896 | strcat ( lDialogString , " --file-filter='" ) ; 897 | for ( i = 0 ; i < aNumOfFileFilters ; i ++ ) 898 | { 899 | strcat ( lDialogString , aFileFilters [ i ] ) ; 900 | strcat ( lDialogString , " " ) ; 901 | } 902 | strcat ( lDialogString , "' --file-filter='All files | *'" ) ; 903 | } 904 | } 905 | else if ( kdialogPresent() ) 906 | { 907 | lWasKdialog = 1 ; 908 | strcpy ( lDialogString , "kdialog --getopenfilename" ) ; 909 | if ( aDefaultPathAndFile && strlen(aDefaultPathAndFile) ) 910 | { 911 | strcat(lDialogString, " \"") ; 912 | strcat(lDialogString, aDefaultPathAndFile ) ; 913 | strcat(lDialogString , "\"" ) ; 914 | } 915 | else 916 | { 917 | strcat(lDialogString, " :" ) ; 918 | } 919 | if ( aNumOfFileFilters > 0 ) 920 | { 921 | strcat(lDialogString , " \"" ) ; 922 | for ( i = 0 ; i < aNumOfFileFilters ; i ++ ) 923 | { 924 | strcat ( lDialogString , aFileFilters [ i ] ) ; 925 | strcat ( lDialogString , " " ) ; 926 | } 927 | strcat ( lDialogString , "\"" ) ; 928 | } 929 | if ( aAllowMultipleSelects ) 930 | { 931 | strcat ( lDialogString , " --multiple --separate-output" ) ; 932 | } 933 | if ( aTitle && strlen(aTitle) ) 934 | { 935 | strcat(lDialogString, " --title \"") ; 936 | strcat(lDialogString, aTitle) ; 937 | strcat(lDialogString, "\"") ; 938 | } 939 | } 940 | else if ( osascriptPresent ( ) ) 941 | { 942 | strcpy ( lDialogString , "osascript -e '" ); 943 | if ( ! aAllowMultipleSelects ) 944 | { 945 | strcat ( lDialogString , "POSIX path of ( " ); 946 | } 947 | else 948 | { 949 | strcat ( lDialogString , "set mylist to " ); 950 | } 951 | strcat ( lDialogString , "choose file " ); 952 | if ( aTitle && strlen(aTitle) ) 953 | { 954 | strcat(lDialogString, "with prompt \"") ; 955 | strcat(lDialogString, aTitle) ; 956 | strcat(lDialogString, "\" ") ; 957 | } 958 | getPathWithoutFinalSlash ( lString , aDefaultPathAndFile ) ; 959 | if ( strlen(lString) ) 960 | { 961 | strcat(lDialogString, "default location \"") ; 962 | strcat(lDialogString, lString ) ; 963 | strcat(lDialogString , "\" " ) ; 964 | } 965 | if ( aNumOfFileFilters > 0 ) 966 | { 967 | strcat(lDialogString , "of type {\"" ); 968 | strcat ( lDialogString , aFileFilters [ 0 ] + 2 ) ; 969 | strcat ( lDialogString , "\"" ) ; 970 | for ( i = 1 ; i < aNumOfFileFilters ; i ++ ) 971 | { 972 | strcat ( lDialogString , ",\"" ) ; 973 | strcat ( lDialogString , aFileFilters [ i ] + 2) ; 974 | strcat ( lDialogString , "\"" ) ; 975 | } 976 | strcat ( lDialogString , "} " ) ; 977 | } 978 | if ( aAllowMultipleSelects ) 979 | { 980 | strcat ( lDialogString , "multiple selections allowed true ' " ) ; 981 | strcat ( lDialogString , "-e 'set mystring to POSIX path of item 1 of mylist' " ); 982 | strcat ( lDialogString , "-e 'repeat with i from 2 to the count of mylist' " ); 983 | strcat ( lDialogString , "-e 'set mystring to mystring & \"|\"' " ); 984 | strcat ( lDialogString , "-e 'set mystring to mystring & POSIX path of item i of mylist' " ); 985 | strcat ( lDialogString , "-e 'end repeat' " ); 986 | strcat ( lDialogString , "-e 'mystring'" ); 987 | } 988 | else 989 | { 990 | strcat ( lDialogString , ")'" ) ; 991 | } 992 | } 993 | else if ( tkinter2Present ( ) ) 994 | { 995 | strcpy ( lDialogString , gPython2Name ) ; 996 | if ( ! isatty ( 1 ) ) 997 | { 998 | strcat ( lDialogString , " -i" ) ; /* for osx without console */ 999 | } 1000 | strcat ( lDialogString , 1001 | " -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();"); 1002 | 1003 | if ( osascriptPresent ( ) ) 1004 | { 1005 | strcat ( lDialogString , 1006 | "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ 1007 | frontmost of process \\\"Python\\\" to true' ''');"); 1008 | } 1009 | strcat ( lDialogString , "lFiles=tkFileDialog.askopenfilename("); 1010 | if ( aAllowMultipleSelects ) 1011 | { 1012 | strcat ( lDialogString , "multiple=1," ) ; 1013 | } 1014 | if ( aTitle && strlen(aTitle) ) 1015 | { 1016 | strcat(lDialogString, "title='") ; 1017 | strcat(lDialogString, aTitle) ; 1018 | strcat(lDialogString, "',") ; 1019 | } 1020 | if ( aDefaultPathAndFile && strlen(aDefaultPathAndFile) ) 1021 | { 1022 | getPathWithoutFinalSlash ( lString , aDefaultPathAndFile ) ; 1023 | if ( strlen(lString) ) 1024 | { 1025 | strcat(lDialogString, "initialdir='") ; 1026 | strcat(lDialogString, lString ) ; 1027 | strcat(lDialogString , "'," ) ; 1028 | } 1029 | getLastName ( lString , aDefaultPathAndFile ) ; 1030 | if ( strlen(lString) ) 1031 | { 1032 | strcat(lDialogString, "initialfile='") ; 1033 | strcat(lDialogString, lString ) ; 1034 | strcat(lDialogString , "'," ) ; 1035 | } 1036 | } 1037 | if ( ( aNumOfFileFilters > 1 ) 1038 | || ( ( aNumOfFileFilters == 1 ) /* test because poor osx behaviour */ 1039 | && ( aFileFilters[0][strlen(aFileFilters[0])-1] != '*' ) ) ) 1040 | { 1041 | strcat(lDialogString , "filetypes=(" ) ; 1042 | for ( i = 0 ; i < aNumOfFileFilters ; i ++ ) 1043 | { 1044 | strcat ( lDialogString , "('','" ) ; 1045 | strcat ( lDialogString , aFileFilters [ i ] ) ; 1046 | strcat ( lDialogString , "')," ) ; 1047 | } 1048 | strcat ( lDialogString , "('All files','*'))" ) ; 1049 | } 1050 | strcat ( lDialogString , ");\ 1051 | \nif not isinstance(lFiles, tuple):\n\tprint lFiles\nelse:\ 1052 | \n\tlFilesString=''\n\tfor lFile in lFiles:\n\t\tlFilesString+=str(lFile)+'|'\ 1053 | \n\tprint lFilesString[:-1]\n\"" ) ; 1054 | } 1055 | else if ( dialogPresent ( ) ) 1056 | { 1057 | strcpy ( lDialogString , "(dialog " ) ; 1058 | if ( aTitle && strlen(aTitle) ) 1059 | { 1060 | strcat(lDialogString, "--title \"") ; 1061 | strcat(lDialogString, aTitle) ; 1062 | strcat(lDialogString, "\" ") ; 1063 | } 1064 | strcat ( lDialogString , "--fselect \"" ) ; 1065 | if ( aDefaultPathAndFile && strlen(aDefaultPathAndFile) ) 1066 | { 1067 | strcat(lDialogString, aDefaultPathAndFile) ; 1068 | } 1069 | strcat(lDialogString, "\" 0 0 >/dev/tty) 2>&1 ; echo >/dev/tty") ; 1070 | } 1071 | else 1072 | { 1073 | tinyfd_messageDialog (gTitle, gMessage,"ok","error",1 ) ; 1074 | return NULL ; 1075 | } 1076 | /* printf ( "lDialogString: %s\n" , lDialogString ) ; //*/ 1077 | if ( ! ( lIn = popen ( lDialogString , "r" ) ) ) 1078 | { 1079 | return NULL ; 1080 | } 1081 | lBuff[0]='\0'; 1082 | p=lBuff; 1083 | while ( fgets ( p , sizeof ( lBuff ) , lIn ) != NULL ) 1084 | { 1085 | p += strlen ( p ); 1086 | } 1087 | pclose ( lIn ) ; 1088 | if ( lBuff[ strlen ( lBuff ) -1 ] == '\n' ) 1089 | { 1090 | lBuff[ strlen ( lBuff ) -1 ] = '\0' ; 1091 | } 1092 | /* printf ( "lBuff: %s\n" , lBuff ) ; //*/ 1093 | if ( lWasKdialog && aAllowMultipleSelects ) 1094 | { 1095 | p = lBuff ; 1096 | while ( ( p = strchr ( p , '\n' ) ) ) 1097 | * p = '|' ; 1098 | } 1099 | /* printf ( "lBuff2: %s\n" , lBuff ) ; //*/ 1100 | if ( ! aAllowMultipleSelects ) 1101 | { 1102 | lIn = fopen( lBuff , "r" ) ; 1103 | if ( ! lIn ) 1104 | { 1105 | return NULL ; 1106 | } 1107 | fclose ( lIn ) ; 1108 | } 1109 | /* printf ( "lBuff3: %s\n" , lBuff ) ; //*/ 1110 | return lBuff ; 1111 | } 1112 | 1113 | 1114 | char const * tinyfd_selectFolderDialog ( 1115 | char const * const aTitle , /* "" */ 1116 | char const * const aDefaultPath ) /* "" */ 1117 | { 1118 | static char lBuff [ MAX_PATH_OR_CMD ] ; 1119 | char lDialogString [ MAX_PATH_OR_CMD ] ; 1120 | DIR * lDir ; 1121 | FILE * lIn ; 1122 | lBuff[0]='\0'; 1123 | 1124 | if ( zenityPresent() ) 1125 | { 1126 | strcpy ( lDialogString , "zenity --file-selection --directory" ) ; 1127 | if ( aTitle && strlen(aTitle) ) 1128 | { 1129 | strcat(lDialogString, " --title=\"") ; 1130 | strcat(lDialogString, aTitle) ; 1131 | strcat(lDialogString, "\"") ; 1132 | } 1133 | if ( aDefaultPath && strlen(aDefaultPath) ) 1134 | { 1135 | strcat(lDialogString, " --filename=\"") ; 1136 | strcat(lDialogString, aDefaultPath) ; 1137 | strcat(lDialogString, "\"") ; 1138 | } 1139 | } 1140 | else if ( kdialogPresent() ) 1141 | { 1142 | strcpy ( lDialogString , "kdialog --getexistingdirectory" ) ; 1143 | if ( aDefaultPath && strlen(aDefaultPath) ) 1144 | { 1145 | strcat(lDialogString, " \"") ; 1146 | strcat(lDialogString, aDefaultPath ) ; 1147 | strcat(lDialogString , "\"" ) ; 1148 | } 1149 | else 1150 | { 1151 | strcat(lDialogString, " :" ) ; 1152 | } 1153 | if ( aTitle && strlen(aTitle) ) 1154 | { 1155 | strcat(lDialogString, " --title \"") ; 1156 | strcat(lDialogString, aTitle) ; 1157 | strcat(lDialogString, "\"") ; 1158 | } 1159 | } 1160 | else if ( osascriptPresent ( ) ) 1161 | { 1162 | strcpy ( lDialogString , "osascript -e 'POSIX path of ( choose folder " ); 1163 | if ( aTitle && strlen(aTitle) ) 1164 | { 1165 | strcat(lDialogString, "with prompt \"") ; 1166 | strcat(lDialogString, aTitle) ; 1167 | strcat(lDialogString, "\" ") ; 1168 | } 1169 | if ( aDefaultPath && strlen(aDefaultPath) ) 1170 | { 1171 | strcat(lDialogString, "default location \"") ; 1172 | strcat(lDialogString, aDefaultPath ) ; 1173 | strcat(lDialogString , "\" " ) ; 1174 | } 1175 | strcat ( lDialogString , ")'" ) ; 1176 | } 1177 | else if ( tkinter2Present ( ) ) 1178 | { 1179 | strcpy ( lDialogString , gPython2Name ) ; 1180 | if ( ! isatty ( 1 ) ) 1181 | { 1182 | strcat ( lDialogString , " -i" ) ; /* for osx without console */ 1183 | } 1184 | strcat ( lDialogString , 1185 | " -c \"import Tkinter,tkFileDialog;root=Tkinter.Tk();root.withdraw();"); 1186 | 1187 | if ( osascriptPresent ( ) ) 1188 | { 1189 | strcat ( lDialogString , 1190 | "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ 1191 | frontmost of process \\\"Python\\\" to true' ''');"); 1192 | } 1193 | 1194 | strcat ( lDialogString , "print tkFileDialog.askdirectory("); 1195 | if ( aTitle && strlen(aTitle) ) 1196 | { 1197 | strcat(lDialogString, "title='") ; 1198 | strcat(lDialogString, aTitle) ; 1199 | strcat(lDialogString, "',") ; 1200 | } 1201 | if ( aDefaultPath && strlen(aDefaultPath) ) 1202 | { 1203 | strcat(lDialogString, "initialdir='") ; 1204 | strcat(lDialogString, aDefaultPath ) ; 1205 | strcat(lDialogString , "'" ) ; 1206 | } 1207 | strcat ( lDialogString , ")\"" ) ; 1208 | } 1209 | else if ( dialogPresent ( ) ) 1210 | { 1211 | strcpy ( lDialogString , "(dialog " ) ; 1212 | if ( aTitle && strlen(aTitle) ) 1213 | { 1214 | strcat(lDialogString, "--title \"") ; 1215 | strcat(lDialogString, aTitle) ; 1216 | strcat(lDialogString, "\" ") ; 1217 | } 1218 | strcat ( lDialogString , "--dselect \"" ) ; 1219 | if ( aDefaultPath && strlen(aDefaultPath) ) 1220 | { 1221 | strcat(lDialogString, aDefaultPath) ; 1222 | } 1223 | strcat(lDialogString, "\" 0 0 >/dev/tty) 2>&1 ; echo >/dev/tty") ; 1224 | } 1225 | else 1226 | { 1227 | tinyfd_messageDialog (gTitle, gMessage,"ok","error",1 ) ; 1228 | return NULL ; 1229 | } 1230 | /* printf ( "lDialogString: %s\n" , lDialogString ) ; //*/ 1231 | if ( ! ( lIn = popen ( lDialogString , "r" ) ) ) 1232 | { 1233 | return NULL ; 1234 | } 1235 | while ( fgets ( lBuff , sizeof ( lBuff ) , lIn ) != NULL ) 1236 | {} 1237 | pclose ( lIn ) ; 1238 | if ( lBuff[ strlen ( lBuff ) -1 ] == '\n' ) 1239 | { 1240 | lBuff[ strlen ( lBuff ) -1 ] = '\0' ; 1241 | } 1242 | /* printf ( "lBuff: %s\n" , lBuff ) ; //*/ 1243 | if ( strlen ( lBuff ) > 0 ) 1244 | { 1245 | lDir = opendir ( lBuff ) ; 1246 | if ( ! lDir ) 1247 | { 1248 | return NULL ; 1249 | } 1250 | closedir ( lDir ) ; 1251 | } 1252 | return lBuff ; 1253 | } 1254 | 1255 | 1256 | /* returns 0 for cancel/no , 1 for ok/yes */ 1257 | int tinyfd_messageDialog ( 1258 | char const * const aTitle , /* NULL or "" */ 1259 | char const * const aMessage , /* NULL or "" */ /* may contain \n and \t */ 1260 | char const * const aDialogType , /* "ok" "okcancel" "yesno"*/ 1261 | char const * const aIconType , /* "info" "warning" "error" "question" */ 1262 | int const aDefaultButton ) /* 0 for cancel/no , 1 for ok/yes */ 1263 | { 1264 | char lBuff [ MAX_PATH_OR_CMD ] ; 1265 | char lDialogString [ MAX_PATH_OR_CMD ] ; 1266 | FILE * lIn ; 1267 | int lResult ; 1268 | lBuff[0]='\0'; 1269 | 1270 | if ( zenityPresent() ) 1271 | { 1272 | strcpy ( lDialogString , "zenity --" ) ; 1273 | if ( ! strcmp( "okcancel" , aDialogType ) ) 1274 | { 1275 | strcat ( lDialogString , 1276 | "question --ok-label=Ok --cancel-label=Cancel" ) ; 1277 | } 1278 | else if ( ! strcmp( "yesno" , aDialogType ) ) 1279 | { 1280 | strcat ( lDialogString , "question" ) ; 1281 | } 1282 | else if ( ! strcmp( "error" , aIconType ) ) 1283 | { 1284 | strcat ( lDialogString , "error" ) ; 1285 | } 1286 | else if ( ! strcmp( "warning" , aIconType ) ) 1287 | { 1288 | strcat ( lDialogString , "warning" ) ; 1289 | } 1290 | else 1291 | { 1292 | strcat ( lDialogString , "info" ) ; 1293 | } 1294 | if ( aTitle && strlen(aTitle) ) 1295 | { 1296 | strcat(lDialogString, " --title=\"") ; 1297 | strcat(lDialogString, aTitle) ; 1298 | strcat(lDialogString, "\"") ; 1299 | } 1300 | if ( aMessage && strlen(aMessage) ) 1301 | { 1302 | strcat(lDialogString, " --text=\"") ; 1303 | strcat(lDialogString, aMessage) ; 1304 | strcat(lDialogString, "\"") ; 1305 | } 1306 | if ( zenity3Present ( ) ) 1307 | { 1308 | strcat ( lDialogString , " --icon-name=dialog-" ) ; 1309 | if ( ! strcmp( "question" , aIconType ) 1310 | || ! strcmp( "error" , aIconType ) 1311 | || ! strcmp( "warning" , aIconType ) ) 1312 | { 1313 | strcat ( lDialogString , aIconType ) ; 1314 | } 1315 | else 1316 | { 1317 | strcat ( lDialogString , "info" ) ; 1318 | } 1319 | } 1320 | strcat ( lDialogString , ";echo $?" ) ; 1321 | } 1322 | else if ( kdialogPresent() ) 1323 | { 1324 | strcpy ( lDialogString , "kdialog --" ) ; 1325 | if ( ( ! strcmp( "okcancel" , aDialogType ) ) 1326 | || ( ! strcmp( "yesno" , aDialogType ) ) ) 1327 | { 1328 | if ( ( ! strcmp( "warning" , aIconType ) ) 1329 | || ( ! strcmp( "error" , aIconType ) ) ) 1330 | { 1331 | strcat ( lDialogString , "warning" ) ; 1332 | } 1333 | strcat ( lDialogString , "yesno" ) ; 1334 | } 1335 | else if ( ! strcmp( "error" , aIconType ) ) 1336 | { 1337 | strcat ( lDialogString , "error" ) ; 1338 | } 1339 | else if ( ! strcmp( "warning" , aIconType ) ) 1340 | { 1341 | strcat ( lDialogString , "sorry" ) ; 1342 | } 1343 | else 1344 | { 1345 | strcat ( lDialogString , "msgbox" ) ; 1346 | } 1347 | strcat ( lDialogString , " \"" ) ; 1348 | if ( aMessage ) 1349 | { 1350 | strcat ( lDialogString , aMessage ) ; 1351 | } 1352 | strcat ( lDialogString , "\"" ) ; 1353 | if ( ! strcmp( "okcancel" , aDialogType ) ) 1354 | { 1355 | strcat ( lDialogString , 1356 | " --yes-label Ok --no-label Cancel" ) ; 1357 | } 1358 | if ( aTitle && strlen(aTitle) ) 1359 | { 1360 | strcat(lDialogString, " --title \"") ; 1361 | strcat(lDialogString, aTitle) ; 1362 | strcat(lDialogString, "\"") ; 1363 | } 1364 | strcat ( lDialogString , ";echo $?" ) ; 1365 | } 1366 | else if ( tkinter2Present ( ) ) 1367 | { 1368 | strcpy ( lDialogString , gPython2Name ) ; 1369 | if ( ! isatty ( 1 ) ) 1370 | { 1371 | strcat ( lDialogString , " -i" ) ; /* for osx without console */ 1372 | } 1373 | 1374 | strcat ( lDialogString , 1375 | " -c \"import Tkinter,tkMessageBox;root=Tkinter.Tk();root.withdraw();"); 1376 | 1377 | if ( osascriptPresent ( ) ) 1378 | { 1379 | strcat ( lDialogString , 1380 | "import os;os.system('''/usr/bin/osascript -e 'tell app \\\"Finder\\\" to set \ 1381 | frontmost of process \\\"Python\\\" to true' ''');"); 1382 | } 1383 | 1384 | strcat ( lDialogString ,"res=tkMessageBox." ) ; 1385 | if ( ! strcmp( "okcancel" , aDialogType ) ) 1386 | { 1387 | strcat ( lDialogString , "askokcancel(" ) ; 1388 | if ( aDefaultButton ) 1389 | { 1390 | strcat ( lDialogString , "default=tkMessageBox.OK," ) ; 1391 | } 1392 | else 1393 | { 1394 | strcat ( lDialogString , "default=tkMessageBox.CANCEL," ) ; 1395 | } 1396 | } 1397 | else if ( ! strcmp( "yesno" , aDialogType ) ) 1398 | { 1399 | strcat ( lDialogString , "askyesno(" ) ; 1400 | if ( aDefaultButton ) 1401 | { 1402 | strcat ( lDialogString , "default=tkMessageBox.YES," ) ; 1403 | } 1404 | else 1405 | { 1406 | strcat ( lDialogString , "default=tkMessageBox.NO," ) ; 1407 | } 1408 | } 1409 | else 1410 | { 1411 | strcat ( lDialogString , "showinfo(" ) ; 1412 | } 1413 | strcat ( lDialogString , "icon='" ) ; 1414 | if ( ! strcmp( "question" , aIconType ) 1415 | || ! strcmp( "error" , aIconType ) 1416 | || ! strcmp( "warning" , aIconType ) ) 1417 | { 1418 | strcat ( lDialogString , aIconType ) ; 1419 | } 1420 | else 1421 | { 1422 | strcat ( lDialogString , "info" ) ; 1423 | } 1424 | strcat(lDialogString, "',") ; 1425 | if ( aTitle && strlen(aTitle) ) 1426 | { 1427 | strcat(lDialogString, "title='") ; 1428 | strcat(lDialogString, aTitle) ; 1429 | strcat(lDialogString, "',") ; 1430 | } 1431 | if ( aMessage && strlen(aMessage) ) 1432 | { 1433 | replaceSubStr ( aMessage , "\n" , "\\n" , lBuff ) ; 1434 | strcat(lDialogString, "message='") ; 1435 | strcat(lDialogString, lBuff) ; 1436 | strcat(lDialogString, "'") ; 1437 | lBuff[0]='\0'; 1438 | } 1439 | strcat(lDialogString, ");\n\ 1440 | if res==False :\n\tprint 1\n\ 1441 | else :\n\tprint 0\n\"" ) ; 1442 | } 1443 | else if ( dialogPresent ( ) || whiptailPresent ( ) ) 1444 | { 1445 | if ( dialogPresent ( ) ) 1446 | { 1447 | strcpy ( lDialogString , "(dialog " ) ; 1448 | } 1449 | else 1450 | { 1451 | strcpy ( lDialogString , "(whiptail " ) ; 1452 | } 1453 | if ( aTitle && strlen(aTitle) ) 1454 | { 1455 | strcat(lDialogString, "--title \"") ; 1456 | strcat(lDialogString, aTitle) ; 1457 | strcat(lDialogString, "\" ") ; 1458 | } 1459 | if ( ! strcmp( "okcancel" , aDialogType ) ) 1460 | { 1461 | strcat ( lDialogString , "--yes-label \"Ok\" --no-label \"No\" --yesno " ) ; 1462 | if ( ! aDefaultButton ) 1463 | { 1464 | strcat ( lDialogString , "--defaultno " ) ; 1465 | } 1466 | } 1467 | else if ( ! strcmp( "yesno" , aDialogType ) ) 1468 | { 1469 | strcat ( lDialogString , "--yesno" ) ; 1470 | if ( ! aDefaultButton ) 1471 | { 1472 | strcat ( lDialogString , "--defaultno " ) ; 1473 | } 1474 | } 1475 | else 1476 | { 1477 | strcat ( lDialogString , "--msgbox" ) ; 1478 | } 1479 | strcat ( lDialogString , " \"" ) ; 1480 | if ( aMessage && strlen(aMessage) ) 1481 | { 1482 | strcat(lDialogString, aMessage) ; 1483 | } 1484 | strcat(lDialogString, 1485 | "\" 0 0 >/dev/tty) 2>&1 ; ( echo $? ) 2>&1 ; echo >/dev/tty") ; 1486 | } 1487 | else if ( notifysendPresent ( ) ) 1488 | { 1489 | strcpy ( lDialogString , "notify-send \"" ) ; 1490 | strcat(lDialogString, gTitle) ; 1491 | strcat ( lDialogString , "\n" ) ; 1492 | strcat(lDialogString, gMessage) ; 1493 | strcat ( lDialogString , "\"" ) ; 1494 | } 1495 | else if ( xmessagePresent ( ) ) 1496 | { 1497 | strcpy ( lDialogString , "xmessage -center \""); 1498 | strcat(lDialogString, gTitle) ; 1499 | strcat ( lDialogString , "\n" ) ; 1500 | strcat(lDialogString, gMessage) ; 1501 | strcat ( lDialogString , "\"" ) ; 1502 | } 1503 | else 1504 | { 1505 | printf ("\n%s\n\n", gTitle); 1506 | printf ("%s\n", gMessage); 1507 | return 0 ; 1508 | } 1509 | 1510 | /* printf ( "lDialogString: %s\n" , lDialogString ) ; //*/ 1511 | if ( ! ( lIn = popen ( lDialogString , "r" ) ) ) 1512 | { 1513 | return 0 ; 1514 | } 1515 | while ( fgets ( lBuff , sizeof ( lBuff ) , lIn ) != NULL ) 1516 | {} 1517 | pclose ( lIn ) ; 1518 | /* printf ( "lBuff: %s len: %d \n" , lBuff , strlen(lBuff) ) ; //*/ 1519 | if ( lBuff[ strlen ( lBuff ) -1 ] == '\n' ) 1520 | { 1521 | lBuff[ strlen ( lBuff ) -1 ] = '\0' ; 1522 | } 1523 | /* printf ( "lBuff1: %s len: %d \n" , lBuff , strlen(lBuff) ) ; //*/ 1524 | lResult = strcmp ( lBuff , "0" ) ? 0 : 1 ; 1525 | /* printf ( "lResult: %d\n" , lResult ) ; //*/ 1526 | return lResult ; 1527 | } 1528 | 1529 | 1530 | /* returns the hexcolor as a string "#FF0000" */ 1531 | /* aoResultRGB also contains the result */ 1532 | /* aDefaultRGB is used only if aDefaultHexRGB is NULL */ 1533 | /* aDefaultRGB and aoResultRGB can be the same array */ 1534 | char const * tinyfd_colorChooser( 1535 | char const * const aTitle , /* NULL or "" */ 1536 | char const * const aDefaultHexRGB , /* NULL or "#FF0000"*/ 1537 | unsigned char aDefaultRGB[3] , /* { 0 , 255 , 255 } */ 1538 | unsigned char aoResultRGB[3] ) /* { 0 , 0 , 0 } */ 1539 | { 1540 | static char lBuff [ 16 ] ; 1541 | char lTmp [ 16 ] ; 1542 | char lDialogString [ MAX_PATH_OR_CMD ] ; 1543 | char lDefaultHexRGB[8]; 1544 | char * lpDefaultHexRGB; 1545 | FILE * lIn ; 1546 | int lWasZenity3 = 0 ; 1547 | int lWasOsascript = 0 ; 1548 | 1549 | lBuff[0]='\0'; 1550 | 1551 | if ( aDefaultHexRGB ) 1552 | { 1553 | Hex2RGB ( aDefaultHexRGB , aDefaultRGB ) ; 1554 | lpDefaultHexRGB = (char *) aDefaultHexRGB ; 1555 | } 1556 | else 1557 | { 1558 | RGB2Hex( aDefaultRGB , lDefaultHexRGB ) ; 1559 | lpDefaultHexRGB = (char *) lDefaultHexRGB ; 1560 | } 1561 | 1562 | if ( zenity3Present() ) 1563 | { 1564 | lWasZenity3 = 1 ; 1565 | sprintf ( lDialogString , 1566 | "zenity --color-selection --show-palette --color=%s" , lpDefaultHexRGB ) ; 1567 | if ( aTitle && strlen(aTitle) ) 1568 | { 1569 | strcat(lDialogString, " --title=\"") ; 1570 | strcat(lDialogString, aTitle) ; 1571 | strcat(lDialogString, "\"") ; 1572 | } 1573 | } 1574 | else if ( kdialogPresent() ) 1575 | { 1576 | sprintf ( lDialogString , 1577 | "kdialog --getcolor --default '%s'" , lpDefaultHexRGB ) ; 1578 | if ( aTitle && strlen(aTitle) ) 1579 | { 1580 | strcat(lDialogString, " --title \"") ; 1581 | strcat(lDialogString, aTitle) ; 1582 | strcat(lDialogString, "\"") ; 1583 | } 1584 | } 1585 | else if ( osascriptPresent ( ) ) 1586 | { 1587 | lWasOsascript = 1 ; 1588 | strcpy ( lDialogString , "osascript -e 'tell app (path to frontmost \ 1589 | application as Unicode text) to set mycolor to choose color default color {"); 1590 | sprintf(lTmp, "%d", 256 * aDefaultRGB[0] ) ; 1591 | strcat(lDialogString, lTmp ) ; 1592 | strcat(lDialogString, "," ) ; 1593 | sprintf(lTmp, "%d", 256 * aDefaultRGB[1] ) ; 1594 | strcat(lDialogString, lTmp ) ; 1595 | strcat(lDialogString, "," ) ; 1596 | sprintf(lTmp, "%d", 256 * aDefaultRGB[2] ) ; 1597 | strcat(lDialogString, lTmp ) ; 1598 | strcat(lDialogString, "}' " ) ; 1599 | strcat ( lDialogString , 1600 | "-e 'set mystring to ((item 1 of mycolor)/256 as integer) as string' " ); 1601 | strcat ( lDialogString , 1602 | "-e 'repeat with i from 2 to the count of mycolor' " ); 1603 | strcat ( lDialogString , 1604 | "-e 'set mystring to mystring & \" \" & \ 1605 | ((item i of mycolor)/256 as integer) as string' " ); 1606 | strcat ( lDialogString , "-e 'end repeat' " ); 1607 | strcat ( lDialogString , "-e 'mystring'"); 1608 | } 1609 | else if ( tkinter2Present ( ) ) 1610 | { 1611 | strcpy ( lDialogString , gPython2Name ) ; 1612 | if ( ! isatty ( 1 ) ) 1613 | { 1614 | strcat ( lDialogString , " -i" ) ; /* for osx without console */ 1615 | } 1616 | 1617 | strcat ( lDialogString , 1618 | " -c \"import Tkinter,tkColorChooser;root=Tkinter.Tk();root.withdraw();"); 1619 | 1620 | if ( osascriptPresent ( ) ) 1621 | { 1622 | strcat ( lDialogString , 1623 | "import os;os.system('''osascript -e 'tell app \\\"Finder\\\" to set \ 1624 | frontmost of process \\\"Python\\\" to true' ''');"); 1625 | } 1626 | 1627 | strcat ( lDialogString , "res=tkColorChooser.askcolor(color='" ) ; 1628 | strcat(lDialogString, lpDefaultHexRGB ) ; 1629 | strcat(lDialogString, "'") ; 1630 | if ( aTitle && strlen(aTitle) ) 1631 | { 1632 | strcat(lDialogString, ",title='") ; 1633 | strcat(lDialogString, aTitle) ; 1634 | strcat(lDialogString, "'") ; 1635 | } 1636 | strcat ( lDialogString , ");\ 1637 | \nif res[1] is not None:\n\tprint res[1]\"" ) ; 1638 | } 1639 | else if ( zenityPresent() ) 1640 | { 1641 | strcpy ( lDialogString , "zenity --entry" ) ; 1642 | if ( aTitle && strlen(aTitle) ) 1643 | { 1644 | strcat(lDialogString, " --title=\"") ; 1645 | strcat(lDialogString, aTitle) ; 1646 | strcat(lDialogString, "\"") ; 1647 | } 1648 | strcat(lDialogString, " --text=\"Enter hex rgb color\nie: #f5ca20\"") ; 1649 | strcat(lDialogString, " --entry-text=\"") ; 1650 | strcat(lDialogString, lpDefaultHexRGB) ; 1651 | strcat(lDialogString, "\"") ; 1652 | } 1653 | else if ( dialogPresent ( ) || whiptailPresent ( ) ) 1654 | { 1655 | if ( dialogPresent ( ) ) 1656 | { 1657 | strcpy ( lDialogString , "(dialog " ) ; 1658 | } 1659 | else 1660 | { 1661 | strcpy ( lDialogString , "(whiptail " ) ; 1662 | } 1663 | if ( aTitle && strlen(aTitle) ) 1664 | { 1665 | strcat(lDialogString, "--title \"") ; 1666 | strcat(lDialogString, aTitle) ; 1667 | strcat(lDialogString, "\" ") ; 1668 | } 1669 | strcat ( lDialogString , 1670 | "--inputbox \"Enter hex rgb color\nie: #f5ca20\" 0 0 \"" ) ; 1671 | strcat(lDialogString, lpDefaultHexRGB) ; 1672 | strcat(lDialogString, "\" >/dev/tty) 2>&1 ; echo >/dev/tty") ; 1673 | } 1674 | else 1675 | { 1676 | tinyfd_messageDialog (gTitle, gMessage,"ok","error",1 ) ; 1677 | return NULL ; 1678 | } 1679 | /* printf ( "lDialogString: %s\n" , lDialogString ) ; //*/ 1680 | if ( ! ( lIn = popen ( lDialogString , "r" ) ) ) 1681 | { 1682 | return NULL ; 1683 | } 1684 | while ( fgets ( lBuff , sizeof ( lBuff ) , lIn ) != NULL ) 1685 | { 1686 | } 1687 | pclose ( lIn ) ; 1688 | if ( ! strlen ( lBuff ) ) 1689 | { 1690 | return NULL ; 1691 | } 1692 | /* printf ( "len Buff: %lu\n" , strlen(lBuff) ) ; //*/ 1693 | /* printf ( "lBuff0: %s\n" , lBuff ) ; //*/ 1694 | if ( lBuff[ strlen ( lBuff ) -1 ] == '\n' ) 1695 | { 1696 | lBuff[ strlen ( lBuff ) -1 ] = '\0' ; 1697 | } 1698 | if ( lWasZenity3 ) 1699 | { 1700 | lBuff[3]=lBuff[5]; 1701 | lBuff[4]=lBuff[6]; 1702 | lBuff[5]=lBuff[9]; 1703 | lBuff[6]=lBuff[10]; 1704 | lBuff[7]='\0'; 1705 | Hex2RGB(lBuff,aoResultRGB); 1706 | } 1707 | else if ( lWasOsascript ) 1708 | { 1709 | sscanf(lBuff,"%hhu %hhu %hhu", & aoResultRGB[0], & aoResultRGB[1],& aoResultRGB[2]); 1710 | RGB2Hex(aoResultRGB,lBuff); 1711 | } 1712 | else 1713 | { 1714 | Hex2RGB(lBuff,aoResultRGB); 1715 | } 1716 | /* printf("%d %d %d\n", aoResultRGB[0],aoResultRGB[1],aoResultRGB[2]); //*/ 1717 | /* printf ( "lBuff: %s\n" , lBuff ) ; //*/ 1718 | return lBuff ; 1719 | } 1720 | 1721 | 1722 | #endif /* _WIN32 */ 1723 | 1724 | -------------------------------------------------------------------------------- /src/tinyfiledialogs.h: -------------------------------------------------------------------------------- 1 | /* 2 | tinyfiledialogs.h 3 | optional unique header file of the tiny file dialogs library - tinyfd 4 | created [November 9, 2014] 5 | Copyright (c) 2014 Guillaume Vareille http://ysengrin.com 6 | http://tinyfiledialogs.sourceforge.net 7 | 8 | tiny file dialogs - tinyfd - version 1.6.0 [January 23, 2015] zlib licence. 9 | Cross-platform dialogs in C/C++ WINDOWS OSX GNOME KDE SOLARIS CONSOLE. 10 | Tested with C & C++ compilers on Windows OSX Linux Freebsd Illumos Solaris. 11 | 12 | A single C file (add it to your project) with 5 modal function calls: 13 | - open file dialog (and multiple files) 14 | - save file dialog 15 | - select folder dialog 16 | - message and question box 17 | - color picker. 18 | 19 | Conceived as a fully independent complement to GLUT, GLUI, SDL and UNITY3D, 20 | it also provides CONSOLE dialogs oin unix. THERE IS NO MAIN LOOP. 21 | 22 | On Windows native code creates the dialogs. 23 | On UNIX it tries successive command line calls: 24 | - zenity -GTK+ desktops: Gnome, Xfce, Unity, Mate, Cinnamon, Lxde 25 | - kdialog -Qt desktops: Kde, Razor 26 | - applescript -mac OSX 27 | - python 2 / tkinter -mac OSX, Solaris CDE, JDS, Window Maker, Enlightenment 28 | - dialog (console / terminal / tty) : part of most unix systems. 29 | The same executable can run across desktops and distributions. 30 | 31 | 32 | This software is provided 'as-is', without any express or implied 33 | warranty. In no event will the authors be held liable for any damages 34 | arising from the use of this software. 35 | 36 | Permission is granted to anyone to use this software for any purpose, 37 | including commercial applications, and to alter it and redistribute it 38 | freely, subject to the following restrictions: 39 | 40 | 1. The origin of this software must not be misrepresented; you must not 41 | claim that you wrote the original software. If you use this software 42 | in a product, an acknowledgment in the product documentation would be 43 | appreciated but is not required. 44 | 2. Altered source versions must be plainly marked as such, and must not be 45 | misrepresented as being the original software. 46 | 3. This notice may not be removed or altered from any source distribution. 47 | */ 48 | 49 | #ifndef TINYFILEDIALOGS_H 50 | #define TINYFILEDIALOGS_H 51 | 52 | /* 53 | if tinydialogs.c is compiled with a C++ compiler 54 | rather than with a C compiler, you need to comment out: 55 | extern "C" { 56 | and the corresponding closing bracket: 57 | } 58 | */ 59 | 60 | #ifdef __cplusplus 61 | extern "C" { 62 | #endif /* __cplusplus */ 63 | 64 | extern int tinyfd_forceConsole ; /* for UNIX only: 0 (default) or 1 */ 65 | /* 1 forces all dialogs into console mode even when the X server is present */ 66 | /* can be modified at run time */ 67 | 68 | char const * tinyfd_saveFileDialog ( 69 | char const * const aTitle , /* "" */ 70 | char const * const aDefaultPathAndFile , /* "" */ 71 | int const aNumOfFileFilters , /* 0 */ 72 | char const * const * const aFileFilters ) ; /* NULL or {"*.txt"} */ 73 | 74 | char const * tinyfd_openFileDialog ( 75 | char const * const aTitle , /* "" */ 76 | char const * const aDefaultPathAndFile , /* "" */ 77 | int const aNumOfFileFilters , /* 0 */ 78 | char const * const * const aFileFilters , /* NULL or {"*.jpg","*.png"} */ 79 | int aAllowMultipleSelects ) ; /* 0 or 1 */ 80 | /* in case of multiple files, the separator is | */ 81 | 82 | char const * tinyfd_selectFolderDialog ( 83 | char const * const aTitle , /* "" */ 84 | char const * const aDefaultPath ) ; /* "" */ 85 | 86 | int tinyfd_messageDialog ( 87 | char const * const aTitle , /* "" */ 88 | char const * const aMessage , /* "" */ /* may contain \n and \t */ 89 | char const * const aDialogType , /* "ok" "okcancel" "yesno" */ 90 | char const * const aIconType , /* "info" "warning" "error" "question" */ 91 | int const aDefaultButton ) ; /* 0 for cancel/no , 1 for ok/yes */ 92 | /* returns 0 for cancel/no , 1 for ok/yes */ 93 | 94 | char const * tinyfd_colorChooser( 95 | char const * const aTitle , /* "" */ 96 | char const * const aDefaultHexRGB , /* NULL or "#FF0000" */ 97 | unsigned char aDefaultRGB[3] , /* { 0 , 255 , 255 } */ 98 | unsigned char aoResultRGB[3] ) ; /* { 0 , 0 , 0 } */ 99 | /* returns the hexcolor as a string "#FF0000" */ 100 | /* aoResultRGB also contains the result */ 101 | /* aDefaultRGB is used only if aDefaultHexRGB is NULL */ 102 | /* aDefaultRGB and aoResultRGB can be the same array */ 103 | 104 | #ifdef __cplusplus 105 | } 106 | #endif /* __cplusplus */ 107 | 108 | #endif /* TINYFILEDIALOGS_H */ 109 | 110 | 111 | /* 112 | - On windows: link against Comdlg32.lib User32.lib and Shell32.lib 113 | - On unix: it tries command line calls, so no such need. 114 | - Use windows separator on windows and unix separator on unix. 115 | - char const * fileFilters[3] = { "*.obj" , "*.stl" , "*.dxf" } ; 116 | - String memory is preallocated statically for all the returned values. 117 | - On unix you need zenity or kdialog or python2/tkinter or dialog installed. 118 | Don't worry, it's already included on most (if not all) desktops. 119 | - If you pass only a path instead of path + filename, 120 | make sure it ends with a separator. 121 | - tinyfd_forceConsole=1; forces all dialogs into console mode. 122 | */ 123 | --------------------------------------------------------------------------------