/**************/ /* filename.c */ /**************************************************************************** PROGRAM: filename.c Windows: Win 3.1 & Win32 (käytä lukemiseen _lread tai fread-funktioita) PURPOSE: Aliohjelmakirjasto tiedoston käsittelyä varten. Tiedoston nimi kysytään standardi dialogilla. AUTHOR: Vesa Lappalainen 20.10.1992 1.8.1994 + tiedostomenun numerointi 21.11.1994 + mahdollisuus kääntää jos vakio TRANSFILE 17.8.1995 + paremmin vanhan tiedoston talletus ja lukeminen USAGE: Käyttäjän pitää 0) Liittää projektiin ALI\mjonot.c, ALI\filename.c ja pääohjelmaan #include "filename.h" 1) Kirjoittaa aliohjelmat MySave, MyRead, MyNew (nimet voi olla muutakin) 2) Alustaa ja varata FileType-tyyppinen tietue 3) Kutsua ikkunafunktiossa menun ID:n perusteella kirjaston funktiota: case CM_FILENEW: NewFile(hWnd,&File); return TRUE; case CM_FILEOPEN: OpenAndReadFile(hWnd,&File); return TRUE; case CM_FILESAVEAS: SaveFileAs(hWnd,&File); return TRUE; case CM_FILESAVE: SaveFile(hWnd,&File); return TRUE; 3b) Jos käytetään TABHAND.H:ta, voidaan laittaa käsittelijät kutsulla: FILE_EVENTS(File,DoClose(msg->hWnd),"OMA.INI"); Missä File on ao sisäinen tiedostokahva ja DoClose on hWnd paramterin ottava sulkemisen suorittava funktio. Lisäksi viestitaulukkoon lisätään EV_HANDLE_CM_FILE, Vakiot CM_FILENEW jne.. on määritelty tiedostossa filename.rh, joka on syytä liittää mukaan tai tehdä siitä kopio omaan .h tiedostoon. 4) Kutsua funktiota Changed(&File) aina kun tiedostoon tulee muutoksia 5) Jos halutaan kääntää language.c:n mukaisesti modulissa esiintyvät asiat, täytyy määritellä vakio TRANSFILE Tällöin kääntyy mm. dialogit ja maskijono sekä ikkunan otsikko (eli näitä ei tällöin saa kääntää ennen kutsua!). ============ Aliohjelmat: (filename.c, filename.h) ============ Changed(*File) - ilmoitetaan, että muutoksia on tullut NewFile(hWnd,*File) - luo uuden tiedoston ja tallettaa mahd. vanhan ExitFile(hWnd,*File) - tekee ohjelman lopuksi tarvittavat toimenpiteet OpenAndReadFile(hWnd,*File) - kysyy nimen ja lukee tiedoston SaveFileAs(hWnd,*File) - kysyy uuden nimen ja tallettaa sille SaveFile(hWnd,*File); - tallettaa tiedoston, jollei nimeä, ks. ed. OpenFileNr(hWnd,*File,nr) - avaa tiedoston numeron perusteella OpenLastFile(hWnd,*File) - avaa edellisen kerran viim. auki olleen tied. OpenFileName(hWnd,*File,name)- avaa tiedoston nimen perusteella SaveOldFilenames(hWnd,ini) - jemmaa menusta vanhat nimet RestoreOldFilenames(hWnd,ini)- laittaa menuun vanhat nimet ============================= Aliohjelmien tarkempi kuvaus: ============================= ---------------------------- int Changed(FileType *File) - ilmoitetaan, että muutoksia on tullut ---------------------------- Funktiolla ilmoitetaan että tiedostoa on muutettu. NewFile yms. käyttävät sitten tätä tietoa, jotta osaavat tarvittaessa kysyä tiedostolle talletuksen ennen vanhan hävittämistä. Käyttäjän pitäisi aina kutsua tätä funktiota, kun tiedostoon on tullut tallettamisen arvoisia muutoksia. Kaikissa seuraavissa parametrit: -------------------------------- Palautus: int - funktiossa sattunut virhe, 0 = OK HWND hWnd - ikkunan kahva FileType *File - osoitin FileType-tietueeseen, jossa tiedoston käyttöön liittyvät tiedot -------------------- NewFile(hWnd,*File) - luo uuden tiedoston ja tallettaa mahd. vanhan -------------------- Jos vanha tiedosto tallettamatta, niin kysyy sen talletuksen (ks. SaveFile) ja sitten luo uuden. Kutsuu käyttäjän aliohjelmaa MyNew. -------------------- ExitFile(hWnd,*File,ini) - tekee ohjelman lopuksi tarvittavat toimenpiteet -------------------- Tallettaa mm. viimeksi käytetyn tiedoston nimen ja tekee FileNew ja tallettaa vanhat nimet ---------------------------- OpenAndReadFile(hWnd,*File) - kysyy nimen ja lukee tiedoston ---------------------------- Kysyy standardi dialogilla tiedoston nimen ja lukee ja avaa sen. Mikäli vanha tallettamatta, kutsutaan aluksi SaveFile. Kutsuu käyttäjän funktiota MyRead. ---------------------------- OpenFileNr(hWnd,*File,nr) - avaa tiedoston numeron perusteella ---------------------------- Kuten edellä, mutta numero tarkoittaa menusta valittua vanhojen tiedostojen numeroa 0-9. Tätä varten täytyy olla vasemman puoleisessa menussa kohdat jotka alkavat "&n ", missä n = 0-9 n:n arvot voivat olla myös joltakin muulta jatkuvalta väliltä, esim. 1-5. Tätä aliohjelmaa kutsutaan yleensä: case IDM_FILE_1: /* FILE-valikon "&1 "-kohdan tunnus case IDM_FILE_2: case IDM_FILE_3: case IDM_FILE_4: case IDM_FILE_5: OpenFileNr(hWnd,&File,wParam-IDM_FILE_0); return TRUE; Aliohjelma on tarkoitettu pitämään yllä edellisten tiedostojen listaa. Lista voidaan tallettaa kutsulla: SaveOldFilenames(hWnd,"OMA.INI"); ja palautetaan seuraavan kerran aluksi kutsulla RestoreOldFilename(hWnd,"OMA.INI"); ----------------------- OpenLastFile(hWnd,*File,ini) - avaa edellisen kerran viim. auki olleen tied. ----------------------- Kuten edellä, mutta nimi otetaan samasta INI-tiedostosta kuin luettiin tiedostojen nimet ----------------------- OpenFileName(hWnd,*File,name)- avaa tiedoston nimen perusteella ----------------------- Tätä käytetään yleensä ohjelman alussa avaamaan tiedosto esim. kutsuparametrin mukaan. ----------------------- SaveFileAs(hWnd,*File) - kysyy uuden nimen ja tallettaa sille ----------------------- Kysyy tiedostolle uuden nimen ja tallettaa sillä. Kutsuu käyttäjän funktiota MySave --------------------- SaveFile(hWnd,*File) - tallettaa tiedoston, jollei nimeä,ks.ed. ---------------------- Tallettaa tiedoston. Jos tiedostolla ei ole nimeä, kysytään sille ensin nimi. ----------------------- SaveOldFilenames(hWnd,ini) - jemmaa menusta vanhat nimet ----------------------- ----------------------- RestoreOldFilenames(hWnd,ini)- laittaa menuun vanhat nimet ----------------------- ================= Makrot: ================= ----------------------- FILE_EVENTS(File,DoClose,IniName) ----------------------- Generoi koko joukon tapahtuman käsittelijöitä CM_FILE??? tapahtumista: static EVENT WM_command_CM_FILENEW(tMSGParam *msg) static EVENT WM_command_CM_FILEOPEN(tMSGParam *msg) static EVENT WM_command_CM_FILESAVEAS(tMSGParam *msg) static EVENT WM_command_CM_FILESAVE(tMSGParam *msg) static EVENT WM_command_CM_FILEEXIT(tMSGParam *msg) static EVENT WM_command_CM_FILEn(tMSGParam *msg) Lisäksi viestit WM_CLOSE ja WM_ENDSESSION käsitellään samalla kutsulla kuin FILEEXIT. CM_FILEEXIT on määritelty: static EVENT WM_command_CM_FILEEXIT(tMSGParam *msg) { if ( ExitFile(msg->hWnd,&File,IniName) ) return 1; if ( DoClose ) return 1; return SendMessage(msg->hWnd,WM_DESTROY,0,0); } Eli esim kutsusta FILE_EVENTS(File,DoClose(msg->hWnd),"OMA.INI"); seuraa käsittelijä: static EVENT WM_command_CM_FILEEXIT(tMSGParam *msg) { if ( ExitFile(msg->hWnd,&File,"OMA.INI") ) return 1; if ( DoClose(msg->hWnd) ) return 1; return SendMessage(msg->hWnd,WM_DESTROY,0,0); } Toisaalta kutsusta FILE_EVENTS(File,NULL,"OMA.INI"); seuraa käsittelijä static EVENT WM_command_CM_FILEEXIT(tMSGParam *msg) { if ( ExitFile(msg->hWnd,&File,"OMA.INI") ) return 1; if ( NULL ) return 1; return SendMessage(msg->hWnd,WM_DESTROY,0,0); } Eli DoClose funktion parametrilista ja (ja nimi) voi olla mikä tahansa tai jos omaa sulkemisfunktiota ei käytetä, niin se voidaan korvata NULL-funktiolla. Oman sulkemisrutiinin täytyy palauttaa 0 jos sulkemista saa jatkaa ja muuta jos ei saa. Myös IniName voi olla NULL, jolloin mitään ei talleteta. ----------------------- EV_HANDLE_CM_FILE, ----------------------- Tapahtuma taulukkoon liitettävä makro, jotta FILE_EVENTS tulee käsiteltyä ================= Käyttäjän tietue: ================= typedef struct FileTypeS{ char Name[128]; // Tiedoston nimi, aluksi "" char Title[40]; // Ikkunan otsikko, jonka perään tied.nimi int Type; // Luetun funktion tyyppi, ks. szFilter char szFilter[256]; // Tunnetut tiedosto-tyypit int changed; // Onko tiedostoa muutettu. LPFNFILE MySave; // Osoitin talletusfunktioon LPFNFILE MyRead; // Osoitin lukufunktioon LPFNFILE MyNew; // Osoitin tiedon nollausfunktioon FAR VOID *data; // Osoitin varsinaiseen dataan WPARAM id; // Mahdollinen DATA-olion id, esim EDIT-ik. HWND hWnd; // Ikkunan kahva, joka käsittelee tiedon } FileType; Esimerkiksi: ------------ static FileType File={ "", // Tiedoston nimi, aluksi "" WTITLE, // Ikkunan otsikko, jonka perään tied.nimi 1, // Luetun funktion tyyppi, ks. szFilter "Laskuri (*.las)\0*.las\0Teksti (*.txt)\0*.txt\0Kaikki (*.*)\0*.*\0", 0, // Onko tiedostoa muutettu. MySave, // Osoitin talletusfunktioon MyRead, // Osoitin lukufunktioon MyNew, // Osoitin tiedon nollausfunktioon laskurit // Osoitin varsinaiseen dataan. }; ================================== Käyttäjän kirjoitettavat funktiot: ================================== ------------------------------------- int MyNew(FileType *File) - uuden luonti ------------------------------------- Tämän funktion tehtävänä on alustaa tyhjiksi ne tiedot, jotka ylläpitävät tiedostoa. Usein riittää pelkkä viimeistä alkiota osoittavan indeksin nollaus tms. Palautettava 0, mikäli homma onnistuu. ------------------------------------- int MyRead(FileType *File) - tiedoston lukeminen ------------------------------------- Tämän funktion täytyy lukea File->Name -niminen tiedosto. Palautettava 0, mikäli homma onnistuu. ------------------------------------- int MySave(FileType *File) - tiedoston talletus ------------------------------------- Tämän funktion täytyy tallettaa tiedosto nimelle File->Name. Palautettava 0, mikäli homma onnistuu. ****************************************************************************/ #include #include #include #include #include #include "filename.h" #include "mjonot.h" #ifdef TRANSFILE #include "language.h" #include "transdlg.h" #else #define T(a) a #endif /***************************************************************************/ /* Lokaali tietorakenne, johon talletetaan nykyisen menun tiedot */ /* &n -alkuisista menukohdista */ #define MAX_MENUS 10 typedef struct { char name[80]; HMENU hMenu; int index; int id; } tFileMenu; static tFileMenu FileMenus[MAX_MENUS+1]; static char LastOpenFile[80]; #define POPUP_ID ((UINT)-1) /***************************************************************************/ static int find_file_hmenu(HMENU hMenu) /* Täyttää lokaalin FileMenus-taulukon */ { int i,n,pos; UINT id; static char s[100]; if ( !hMenu ) return 1; n = GetMenuItemCount(hMenu); if ( n<=0 ) return 1; for (i=0; i 0 && s[0] == '&' && ( pos=paikka("0123456789",s[1]) ) >= 0 && s[2] == ' ' && pos < MAX_MENUS ) { FileMenus[pos].hMenu = hMenu; kopioi_jono(N_S(FileMenus[pos].name),s+3); FileMenus[pos].index=i; FileMenus[pos].id=id; } } return 0; } /***************************************************************************/ static int UpdateFileMenu(HWND hWnd) /* Päivitetään menu taulukkoa vastaavaksi. */ { int i; char s[100]; for (i=0; ii1; i--) strcpy(FileMenus[i].name,FileMenus[i-1].name); kopioi_jono(N_S(FileMenus[i1].name),filename); return 0; } /***************************************************************************/ static int AddFileMenu(HWND hWnd,char *filename) /* Lisätään nimi File-menun numeroitujen kohtien ekaksi. Jos nimi ** on tyhjä tai NULL, päivitetään pelkästään tietorakenne ---------------------------------------------------------------------------*/ { HMENU hMenu=GetMenu(hWnd); int i; for (i=0; iName); if ( File->Title ) { for (p=lbs;*p;p++) { if ( *p == '\\' ) lbs = p+1; *p = pieneksi(*p); } wsprintf(s,"%s - [%s]",(LPSTR)T(File->Title),lbs); SetWindowText(File->hWnd,s); #ifdef TRANSFILE SetNoTranslate("* - [*]"); #endif } return 0; } /***************************************************************************/ #ifdef TRANSFILE static LPSTR translate_filter(LPCSTR s) { static char filter[200]; char part[100]; char *f = filter; const char *tp; int pi,i,mi = sizeof(filter)-1; LPCSTR p = s; i = 0; while ( *p ) { f[i] = 0; _fstrncpy(part,p,sizeof(part)-1); part[sizeof(part)-1] = 0; tp = T(part); pi = _fstrlen(tp); if ( i+pi >= mi ) break; _fstrcpy(&f[i],tp); i += pi+1; p += strlen(part)+1; } f[i] = 0; return filter; } #else #define translate_filter #endif #define OPEN 0 #define SAVE 1 #define SAVEAS 2 /***************************************************************************/ static int AskFileName(HWND hwnd,int save,FileType *File) /* ** Kysytään tiedoston nimi. Jos save, niin tallettamista varten, muuten ** lukemista varten. Kutsussa käytetään edellisen kutsun hakemistoa ** ja tiedoston tyyppiä (joka samalla määrää jokerin). Tyyppilistan ** alkuun tulee käyttäjän kirjoittama jokeri, joka säilyy, mikäli ** listan ensimmäinen on valittu. */ { static OPENFILENAME ofn; static char szDirName[256]="."; static char szFile[256]="", szFileTitle[256]; static char szCustomFilter[256]="(*.*)\0*.*\0"; static int FirstTime = 1; /* 1. kutsu tulossa. */ if ( FirstTime ) { /* Onko 1. kutsu? */ FirstTime = 0; /* Alustetaan tietue 1. kerralla. */ memset(&ofn, 0, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrCustomFilter = szCustomFilter; ofn.nMaxCustFilter = sizeof(szCustomFilter); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrInitialDir = szDirName; } else if ( save != OPEN ) kopioi_jono(N_S(szFile),File->Name); else szFile[0]=0; ofn.lpstrFilter = translate_filter(File->szFilter); ofn.hwndOwner = hwnd; ofn.Flags = OFN_SHOWHELP | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; if ( save == SAVEAS ) ofn.Flags |= OFN_OVERWRITEPROMPT; if ( ( save == SAVE ? GetSaveFileName(&ofn) : save == SAVEAS ? GetSaveFileName(&ofn) : save == OPEN ? GetOpenFileName(&ofn) : FALSE ) == TRUE ) { set_title(File,ofn.lpstrFileTitle); if ( strlen(szFile) >= sizeof(File->Name) ) return -2; /* Ei tilaa! */ strcpy(File->Name,szFile); return 0; } if ( CommDlgExtendedError() == 0 ) return -1; /* Painettu esim. CANCEL */ return -3; } /***************************************************************************/ int Changed(FileType *File) { File->changed = 1; return 0; } /***************************************************************************/ int SaveFile(HWND hWnd,FileType *File) /* ** Talletetaan tiedosto. Jos tiedostolla ei ole nimeä, kysytään nimi ** ensin. Itse talletuksessa käytetään käyttäjän funktiota, mikäli ** tälläinen on olemassa. Jos käyttäjän funktiota ei ole, palautetaan ** tieto siitä, että talletus onnistui ja käyttäjän on itse tallettettava ** tieto. */ { int error; File->hWnd = hWnd; if ( !File->Name[0] && (error = AskFileName(hWnd,SAVE,File)) != 0 ) return error; if ( !File->MySave || !(error = File->MySave(File)) ) { File->changed = 0; AddFileMenu(hWnd,File->Name); return 0; } return error; } /***************************************************************************/ int SaveFileAs(HWND hWnd,FileType *File) /* ** Talletetaan tiedosto. Kysytään nimi ensin. Itse talletuksessa käytetään ** käyttäjän funktiota, mikäli tälläinen on olemassa. */ { int error; File->hWnd = hWnd; if ( !( error = AskFileName(hWnd,SAVEAS,File) ) ) return SaveFile(hWnd,File); return error; } /***************************************************************************/ static int ClearFileName(HWND hWnd,FileType *File,int ClearName) { File->changed = 0; if ( ClearName ) { File->Name[0] = 0; if ( File->Title[0] ) SetWindowText(hWnd,T(File->Title)); } return 0; } /***************************************************************************/ int SaveFileIfChanged(HWND hWnd,FileType *File,int ClearName) /* ** Jos tiedostoa on muutettu, kysytään sen tallettaminen. ** Jos tallettamiseen vastataan YES, talletetaan ** jos NO, ei talleteta ** jos CANCEL, palautetaan virhe ** Jos kaikki onnistuu ja ClearFile=1, niin tyhjennetaan ikkunan otsikko ** ja tiedoston nimi ja palautetaan 0. */ { char s[256]; int answ; int error; File->hWnd = hWnd; if ( !File->changed ) return ClearFileName(hWnd,File,ClearName); wsprintf(s,T("%s\nTiedostoa muutettu.\n\nTalletetaanko?"), (LPSTR)File->Name); answ = MessageBox(hWnd,s,T(File->Title),MB_APPLMODAL | MB_YESNOCANCEL | MB_ICONEXCLAMATION); switch ( answ ) { case IDYES: if ( (error = SaveFile(hWnd,File)) != 0 ) return error; case IDNO: return ClearFileName(hWnd,File,ClearName); default: return -1; } } /***************************************************************************/ int NewFile(HWND hWnd,FileType *File) /* Talletetaan ensin tarvittaessa vanha tiedosto ja tyhjennetään data-alue.*/ { int error; File->hWnd = hWnd; error = SaveFileIfChanged(hWnd,File,1); if ( error ) return error; UpdateLastName(""); if ( !File->MyNew ) return 0; error = File->MyNew(File); File->changed = 0; return error; } /***************************************************************************/ static int DoReadFile(FileType *File) { int error; set_title(File,File->Name); File->changed = 0; if ( File->Name[0] == 0 ) return NewFile(File->hWnd,File); if ( File->MyRead ) { error = File->MyRead(File); File->changed = 0; if ( error != 0 ) return error; } AddFileMenu(File->hWnd,File->Name); return 0; } /***************************************************************************/ int OpenAndReadFile(HWND hWnd,FileType *File) /* Talletetaan tarvittaessa ensin vanha tiedosto ja avataan ja luet.uusi. */ { int error; File->hWnd = hWnd; if ( (error = SaveFileIfChanged(hWnd,File,0) ) != 0 ) return error; if ( (error = AskFileName(hWnd,OPEN,File) ) != 0 ) return error; return DoReadFile(File); } /***************************************************************************/ int OpenFileName(HWND hWnd,FileType *File,const char *name) /* Talletetaan tarvittaessa ensin vanha tiedosto ja avataan ja luet.uusi. */ { int error; File->hWnd = hWnd; if ( (error = SaveFileIfChanged(hWnd,File,0) ) != 0 ) return error; AddFileMenu(hWnd,NULL); kopioi_jono(N_S(File->Name),name); return DoReadFile(File); } /***************************************************************************/ int OpenFileNr(HWND hWnd,FileType *File,int nr) /* Talletetaan tarvittaessa ensin vanha tiedosto ja avataan ja luet.uusi. */ { if ( FileMenus[nr].hMenu == 0 || FileMenus[nr].name[0] == 0 ) return -6; return OpenFileName(hWnd,File,FileMenus[nr].name); } /***************************************************************************/ int OpenLastFile(HWND hWnd,FileType *File,const char *ini) { char name[80]; GetPrivateProfileString(OLDNAMES,"open","",N_S(name),ini); UpdateLastName(name); if ( name[0] == 0 ) return NewFile(hWnd,File); OpenFileName(hWnd,File,name); return 0; } /***************************************************************************/ int ExitFile(HWND hWnd,FileType *File,const char *ini) /* Talletetaan ensin tarvittaessa vanha tiedosto */ { SaveOldFilenames(hWnd,ini); if ( ini ) WritePrivateProfileString(OLDNAMES,"open",GetLastName(),ini); return NewFile(hWnd,File); } /***************************************************************************/ int SaveOldFilenames(HWND hWnd,const char *ini) { int i; char s[20]; if ( ini == NULL ) return 0; AddFileMenu(hWnd,NULL); for (i=0; i=0; i--) { sprintf(s,"old%d",i); if ( GetPrivateProfileString(OLDNAMES,s,"",N_S(name),ini) == 0 ) continue; AddName(name); } UpdateFileMenu(hWnd); return 0; }