//-----------------------------------------------------------------------------
// Velmi jednoduchy prohlizec souboru typu PLT upraveny pro pouziti se soubory
// urcenymi pro plotter HP 7475
//
// Autor: Pavel Tisnovsky
//
//-----------------------------------------------------------------------------

#ifdef __BORLANDC__
#include <windows.h>
#endif

#include <GL/glut.h>                            // hlavickovy soubor funkci GLUTu a OpenGL
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>

#define WINDOW_TITLE    "simple HPGL viewer"    // titulek okna
#define WINDOW_WIDTH    800                     // pocatecni velikost okna
#define WINDOW_HEIGHT   600

// typ prikazu
typedef enum {
    PEN_UP,                     // zvednuti kresliciho pera
    PEN_DOWN,                   // spusteni kresliciho pera
    MOVE_TO,                    // presun kresliciho pera
    SELECT_PEN                  // vyber kresliciho pera ~ vyber barvy
} Command;

// jeden zaznam ziskany ze souboru HPGL
typedef struct Item {
    Command cmd;                // prikaz - (PEN_UP, PEN_DOWN, MOVE_TO, SELECT_PEN)
    double  x;                  // souradnice
    double  y;
    int     pen;                // barva pera
    struct Item *next;          // vazba v linearnim seznamu
} Item;

double scale=1.0;
double xpos=0.0;
double ypos=0.0;
int redraw=1;

Item *p_first;                  // ukazatele v linearnim seznamu
Item *p_last;

// informace o okne
struct {
    int width;
    int height;
} window={WINDOW_WIDTH, WINDOW_HEIGHT};

// informace o mysi
struct {
    int status;
    int xtran0, xtran1, xtran2;
    int ytran0, ytran1, ytran2;
    int ztran0, ztran1, ztran2;
} mouse={0, 0, 0, 0, 0, 0, 0, 0, 0, 0};



//-----------------------------------------------------------------------------
// Inicializace linearniho seznamu
//-----------------------------------------------------------------------------
void initItem(void)
{
    p_first=NULL;
    p_last=NULL;
}



//-----------------------------------------------------------------------------
// Vlozeni polozky do linearniho seznamu
//-----------------------------------------------------------------------------
void addItem(Command cmd, double x, double y, int pen)
{
    Item *item=(Item*)malloc(sizeof(Item));
    item->cmd=cmd;
    item->x=x;
    item->y=y;
    item->pen=pen;
    item->next=NULL;            // prvek bude umisten na konci seznamu
    // seznam je prazdny
    if (p_first==NULL) {
        p_first=item;           // prvni i posledni prvek
        p_last=item;            // seznamu jsou shodne
    }
    // pridani polozky do neprazdneho seznamu
    else {
        p_last->next=item;      // zretezeni prvku na konec seznamu
        p_last=item;            // aktualizace ukazatele na posledni prvek
        /*
        Item *p=p_first;
        while (p->next !=NULL)
            p=p->next;
        p->next=item;
        */
    }
}



//-----------------------------------------------------------------------------
// Prescteni HPGL souboru urceneho pro HP7475
//-----------------------------------------------------------------------------
int readItems(char *fileName)
{
    FILE *fin=fopen(fileName, "rt");
    int c;
    char s[100];
    if (!fin) return 0;
    // smycka, ve ktere se zanalyzuje cely soubor HPGL
    do {
        int i;
        // nacteni jednoho prikazu (oddelovacem je vetsinou strednik)
        for (i=0; (c=fgetc(fin))!=';' && c!=EOF; i++) {
            // tato podminka je nutna pro ovladace,
            // ktere generuji kod ve stylu 'PUPA10,10'
            // misto 'PU;PA10,10
            if (i==2 && isalpha(c)) {
                ungetc(c, fin);
                break;
            }
            // prevod znaku na velka pismena (pro jistotu)
            s[i]=(c>='a' && c<='z') ? c-'a'+'A':c;
        }
        s[i]=0;                             // ukonceni retezce
        if (!strcmp(s, "PU")) {
            addItem(PEN_UP, 0, 0, 0);
        }
        if (!strcmp(s, "PD")) {
            addItem(PEN_DOWN, 0, 0, 0);
        }
        if (!strncmp(s, "SP", 2)) {         // prikaz SP
            int pen=strtol(s+2, NULL, 10);  // = nacteme barvu pera
            addItem(SELECT_PEN, 0, 0, pen);
        }
        if (!strncmp(s, "PA", 2)) {         // prikaz PA
            double x, y;                    // = musime nacist obe souradnice
            for (i=0; s[i]!='-' && s[i]!='+' && !isdigit(s[i]); i++)
                ;
            x=strtod(s+i, NULL);
            i++;
            for (; s[i]!='-' && s[i]!='+' && s[i]!=','; i++)
                ;
            i++;
            y=strtod(s+i, NULL);
            addItem(MOVE_TO, x, y, 0);
        }
    } while (c!=EOF);                       // cteni az do konce souboru
    fclose(fin);
    return 1;
}



//-----------------------------------------------------------------------------
// Prekresleni vektorove kresby
//-----------------------------------------------------------------------------
void redrawDrawing(double scale,                    // meritko obrazce
                   double xpos,                     // posun obrazce
                   double ypos)
{
    Item *item=p_first;
    double x1=0, y1=0, x2=0, y2=0;                  // souradnice pro vypocet usecek
    float  r=1.0, g=1.0, b=1.0;                     // barva kresliciho pera
    float  scale2=mouse.ztran0/100.0+1.0;           // zmena meritka zadana mysi
    Command pen=PEN_DOWN;

    // posun a zmena meritka kresby
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslatef(xpos, ypos, 0);                    // posun zadany klavesnici
    glTranslatef(mouse.xtran0, -mouse.ytran0, 0);   // posun zadany mysi
    // posunuti pocatku (pro zmenu meritka vuci stredu okna
    // je nutna mala programova uprava)
    glTranslatef(window.width/2, window.height/2, 0);
    glScalef(scale, scale, scale);                  // zmena meritka zadana klavesnici
    glScalef(scale2, scale2, scale2);               // zmena meritka zadana mysi
    glScalef(0.1, 0.1, 0.1);
    glTranslatef(-window.width/2, -window.height/2, 0);

    // projit celym seznamem a aplikovat v nem ulozene prikazy
    while (item!=NULL) {
        Command cmd=item->cmd;                      // nacist prikaz
        double x=item->x;                           // nacist souradnice
        double y=item->y;
        int    pencolor=item->pen;                  // nacist barvu pera
        item=item->next;                            // prechod na dalsi polozku
        switch (cmd) {
            case PEN_UP:                            // zvednuti kresliciho pera
                pen=PEN_UP;
                break;
            case PEN_DOWN:                          // polozeni kresliciho pera
                pen=PEN_DOWN;
                break;
            case MOVE_TO:                           // zmena pozice kresliciho pera
                if (pen==PEN_UP) {                  // PEN_UP - presun bez kresleni
                    x1=x;
                    y1=y;
                }
                else {                              // PEN_DOWN - kreslime
                    x2=x;
                    y2=y;
                    glColor3f(r, g, b);             // nastavit barvu
                    glBegin(GL_LINES);              // a vykreslit usecku
                        glVertex2d(x1, y1);
                        glVertex2d(x2, y2);
                    glEnd();
                    x1=x;                           // zapamatovat si pozici
                    y1=y;                           // pro dalsi MOVE_TO
                }
                break;
            case SELECT_PEN:                        // zmena barvy pera
                {
                    static float palette[][3]={     // static=alokace 1x za behu
                        {0.0, 0.0, 1.0},
                        {0.0, 1.0, 0.0},
                        {0.0, 1.0, 1.0},
                        {1.0, 0.0, 0.0},
                        {1.0, 0.0, 1.0},
                        {1.0, 1.0, 0.0},
                        {1.0, 1.0, 1.0},
                        {0.5, 0.5, 0.5},
                    };
                    if (pencolor>=0 && pencolor<=7) { // HP7475 ma osm per
                        r=palette[pencolor][0];
                        g=palette[pencolor][1];
                        b=palette[pencolor][2];
                    }
                    else {                          // kazdy jiny index=bila barva
                        r=1.0;
                        g=1.0;
                        b=1.0;
                    }
                }
                break;
            default:                                // ostatni prikazy ignorujeme
                break;
        }
    }
}



//-----------------------------------------------------------------------------
// Funkce volana pro inicializaci vykreslovani
//-----------------------------------------------------------------------------
void onInit(void)
{
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);       // barva pozadi
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);      // mod ulozeni pixelu
    glPointSize(1.0f);
    glLineWidth(1.0f);
    glEnable(GL_POINT_SMOOTH);
    glEnable(GL_LINE_SMOOTH);
}



//-----------------------------------------------------------------------------
// Nastaveni souradneho systemu v zavislosti na velikosti okna
//-----------------------------------------------------------------------------
void onResize(int w, int h)                     // argumenty w a h reprezentuji novou velikost okna
{
    glViewport(0, 0, w, h);                     // viditelna oblast pres cele okno
    glMatrixMode(GL_PROJECTION);                // zacatek modifikace projekcni matice
    glLoadIdentity();                           // vymazani projekcni matice (=identita)
    glOrtho(0, w, 0, h, -1, 1);                 // mapovani abstraktnich souradnic do souradnic okna
    window.width=w;
    window.height=h;
}



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri kazdem prekresleni okna
//-----------------------------------------------------------------------------
void onDisplay(void)
{
    glClear(GL_COLOR_BUFFER_BIT);               // vymazani vsech bitovych rovin barvoveho bufferu
    glDrawBuffer(GL_BACK);                      // pixmapa se bude kreslit do zadniho barvoveho bufferu
    redrawDrawing(scale, xpos, ypos);
    glFlush();                                  // provedeni a vykresleni vsech zmen
    glutSwapBuffers();
}



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri stlaceni ASCII klavesy
//-----------------------------------------------------------------------------
#ifdef __BORLANDC__
#pragma option -w-par
#endif
void onKeyboard(unsigned char key, int x, int y)
{
    key=(key>='A' && key<='Z') ? key-'A'+'a': key;
    if (key>='0' && key<='9') { glutPostRedisplay(); } // nastaveni barvove palety
    switch (key) {
        case 27:               // pokud byla stlacena klavesa ESC, konec programu
        case 'q': exit(0); break; // totez co klavesa ESC
        default:           break;
    }
}
#ifdef __BORLANDC__
#pragma option -w+par
#endif



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri stlaceni non-ASCII klavesy
//-----------------------------------------------------------------------------
#ifdef __BORLANDC__
#pragma option -w-par
#endif
void onSpecial(int key, int x, int y)
{
    // posun kresby a zmena meritka
    switch (key) {
        case GLUT_KEY_LEFT:      xpos-=25;   redraw=1; glutPostRedisplay(); break;
        case GLUT_KEY_RIGHT:     xpos+=25;   redraw=1; glutPostRedisplay(); break;
        case GLUT_KEY_UP:        ypos+=25;   redraw=1; glutPostRedisplay(); break;
        case GLUT_KEY_DOWN:      ypos-=25;   redraw=1; glutPostRedisplay(); break;
        case GLUT_KEY_PAGE_UP:   scale*=1.1; redraw=1; glutPostRedisplay(); break;
        case GLUT_KEY_PAGE_DOWN: scale/=1.1; redraw=1; glutPostRedisplay(); break;
        default:                                                                                 break;
    }
}
#ifdef __BORLANDC__
#pragma option -w+par
#endif



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri stlaceni ci pusteni tlacitka mysi
//-----------------------------------------------------------------------------
void onMouse(int button, int state, int x, int y)
{
    switch (button) {
        case GLUT_LEFT_BUTTON:          // posun kresby
            if (state==GLUT_DOWN) {
                mouse.status=1;
                mouse.xtran1=x;
                mouse.ytran1=y;
            }
            else {
                mouse.status=0;
                mouse.xtran2=mouse.xtran0;
                mouse.ytran2=mouse.ytran0;
            }
            break;
        case GLUT_RIGHT_BUTTON:         // zmena meritka kresby
            if (state==GLUT_DOWN) {
                mouse.status=2;
                mouse.ztran1=y;
            }
            else {
                mouse.status=0;
                mouse.ztran2=mouse.ztran0;
            }
            break;
        default:
            break;
    }
}



//-----------------------------------------------------------------------------
// Tato callback funkce je zavolana pri posunu kurzoru mysi
//-----------------------------------------------------------------------------
void onMouseMotion(int x, int y)
{
    switch (mouse.status) {
        case 1:                 // posun kresby
            mouse.xtran0=mouse.xtran2+x-mouse.xtran1;
            mouse.ytran0=mouse.ytran2+y-mouse.ytran1;
            glutPostRedisplay();
            break;
        case 2:                 // zmena meritka kresby
            mouse.ztran0=mouse.ztran2+y-mouse.ztran1;
            glutPostRedisplay();
            break;
        default:
            break;
    }
}



//-----------------------------------------------------------------------------
// Hlavni funkce konzolove aplikace
//-----------------------------------------------------------------------------
int main(int argc, char **argv)
{
    glutInit(&argc, argv);                      // inicializace knihovny GLUT
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
    glutCreateWindow(WINDOW_TITLE);             // vytvoreni okna pro kresleni
    glutReshapeWindow(WINDOW_WIDTH, WINDOW_HEIGHT);// zmena velikosti okna
    glutPositionWindow(100, 100);               // pozice leveho horniho rohu okna
    glutDisplayFunc(onDisplay);                 // registrace funkce volane pri prekreslovani okna
    glutReshapeFunc(onResize);                  // registrace funkce volane pri zmene velikosti okna
    glutKeyboardFunc(onKeyboard);               // registrace funkce volane pri stlaceni klavesy
    glutSpecialFunc(onSpecial);                 // registrace funkce volane pri stlaceni specialni klavesy
    glutMouseFunc(onMouse);                     // funkce volana pri stlaceni tlacitka mysi
    glutMotionFunc(onMouseMotion);              // funkce volana pri posunu mysi
    onInit();                                   // inicializace vykreslovani
    if (!readItems(argv[1])) return 0;          // nacteni celeho souboru s kresbou
    glutMainLoop();                             // nekonecna smycka, kde se volaji zaregistrovane funkce
    return 0;                                   // navratova hodnota vracena operacnimu systemu
}



//-----------------------------------------------------------------------------
// finito
//-----------------------------------------------------------------------------