From pouchintv-svn at baysse.fr Wed Dec 5 00:15:16 2007 From: pouchintv-svn at baysse.fr (pouchintv-svn at baysse.fr) Date: Wed, 5 Dec 2007 00:15:16 +0100 (CET) Subject: [Pouchintv-dev] [PouchinTVMod] gingko | r103 - trunk Message-ID: <20071204231516.BB2215F2E0@mail.baysse.fr> Author: gingko Date: 2007-12-05 00:15:16 +0100 (Wed, 05 Dec 2007) New Revision: 103 Modified: trunk/base.h trunk/main.cpp trunk/record.cpp trunk/record.h trunk/recprog.cpp trunk/recprog.h trunk/resource.h trunk/search.cpp trunk/settings.cpp Log: Ajout de code pour supprimer les espaces de fin dans les noms de p?\195?\169riph?\195?\169riques r?\195?\169cup?\195?\169r?\195?\169s depuis Windows, afin de pallier ?\195?\160 un bug de driver rapport?\195?\169 par un utilisateur. J'en profite pour livrer quelques optimisations que j'avais initialement pr?\195?\169vu d'inclure dans ma prochaine mise ?\195?\160 jour, ?\195?\167a fera autant de changements en moins la prochaine fois. Modified: trunk/base.h =================================================================== --- trunk/base.h 2007-11-17 21:25:07 UTC (rev 102) +++ trunk/base.h 2007-12-04 23:15:16 UTC (rev 103) @@ -85,7 +85,9 @@ // Enregistrer DShow #define LOG_DSHOW 0 -// Nombre max de chaines +// Nombre max de chaînes +#define NB_MAX_CHAINES 500 +// Nombre max de pistes son par chaîne #define NB_MAX_PISTES 32 // Nombre max d'enregistrements simultanés sur un même multiplex @@ -167,6 +169,11 @@ #endif // #if USE_VMR9 +// Macros utilitaires pour améliorer la lisibilité du code : +#define _cmd_ MAKEWPARAM +#define _ncode_(lParam) LPNMHDR(lParam)->code + + const COLORREF colorKey = RGB(0,0,1); const GUID guid_H264 = { Modified: trunk/main.cpp =================================================================== --- trunk/main.cpp 2007-11-17 21:25:07 UTC (rev 102) +++ trunk/main.cpp 2007-12-04 23:15:16 UTC (rev 103) @@ -91,6 +91,16 @@ // Délai de persistance du curseur (pointeur souris) en mode plein écran : #define DUREE_CURSEUR 3000 // Valeur en millisecondes +// Positions des sous-menus du menu principal : +#define MENU_FICHIER_POSITION 0 +#define MENU_FILTRES_POSITION 1 +#define MENU_COMMANDES_POSITION 2 +#define MENU_CHAINES_POSITION 3 +#define MENU_PISTES_POSITION 4 +#define MENU_CAPTURES_POSITION 5 +#define MENU_PROGRAMME_POSITION 6 +#define SMENU_PRIORITE_POSITION 2 // dans menu "Programme" + static DWORD last_zapping = 0; static DWORD last_volume_osd = 0; static DWORD last_zoom = 0; @@ -272,7 +282,7 @@ // On vérifie que l'entrée n'existe pas if (!mi_exists) { // Sélectionne le menu "Priorités" - HMENU hMenuPriorite = GetSubMenu(GetSubMenu(hMenu, 6), 2); + HMENU hMenuPriorite = GetSubMenu(GetSubMenu(hMenu, MENU_PROGRAMME_POSITION), SMENU_PRIORITE_POSITION); // Ajoute l'entrée dans le menu AppendMenu(hMenuPriorite, MF_CHECKED, IDM_IDLE_PRIORITY, L"Basse"); } @@ -392,6 +402,8 @@ /** * Recherche le numéro du menu en enlevant les "&" le cas échéant **/ +/* +// [pas utile aussi longtemps que les positions des sous-menus sont constantes] static int cherche_menu(const HMENU hMenu, LPCWSTR nom_menu) { LPWSTR cherche_strip = new wchar_t[wcslen(nom_menu)+1]; @@ -446,6 +458,7 @@ myprintf(L"cherche_menu(\"%s\") = %d\n", nom_menu, ret); return ret; } +*/ static void update_pistes_menu(HMENU hMenu) { @@ -470,12 +483,13 @@ }; const Chaine & canal_courant = Canaux[ixChaineCourante]; - UINT position_menu = cherche_menu(hMenu, L"Pistes"); + // UINT position_menu = cherche_menu(hMenu, L"Pistes"); // Récupère le nom du menu avant de l'effacer wchar_t nom_menu[64]; - GetMenuString(hMenu, position_menu, nom_menu, _countof(nom_menu), MF_BYPOSITION); - DeleteMenu(hMenu, position_menu, MF_BYPOSITION); + GetMenuString(hMenu, MENU_PISTES_POSITION/*position_menu*/, + nom_menu, _countof(nom_menu), MF_BYPOSITION); + DeleteMenu(hMenu, MENU_PISTES_POSITION/*position_menu*/, MF_BYPOSITION); HMENU pistes = CreatePopupMenu(); @@ -498,21 +512,23 @@ swprintf_s(tab, _countof(tab), L"Audio %i %S (%s)", i+1, langue, (son_courant.type==1) ? L"AC3" : L"MPG"); - AppendMenu(pistes, (i==ixSonCourant) ? MF_CHECKED : MF_UNCHECKED, 500+i, tab); + AppendMenu(pistes, (i==ixSonCourant) ? MF_CHECKED : MF_UNCHECKED, IDM_PISTES_BASE+i, tab); } - InsertMenu(hMenu, position_menu, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT_PTR)pistes, nom_menu); + InsertMenu(hMenu, MENU_PISTES_POSITION/*position_menu*/, + MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT_PTR)pistes, nom_menu); } /** * Met à jour la liste des chaînes **/ static void update_chaines_menu(HMENU hMenu) { - UINT position_menu = cherche_menu(hMenu, L"Chaînes"); + // UINT position_menu = cherche_menu(hMenu, L"Chaînes"); // Récupère le nom du menu avant de l'effacer wchar_t nom_menu[64]; - GetMenuString(hMenu, position_menu, nom_menu, _countof(nom_menu), MF_BYPOSITION); - DeleteMenu(hMenu, position_menu, MF_BYPOSITION); + GetMenuString(hMenu, MENU_CHAINES_POSITION/*position_menu*/, + nom_menu, _countof(nom_menu), MF_BYPOSITION); + DeleteMenu(hMenu, MENU_CHAINES_POSITION/*position_menu*/, MF_BYPOSITION); HMENU chaines = CreatePopupMenu(); bool _recording = recording(); @@ -531,20 +547,26 @@ if (_recording && canal.TSID != Canaux[ixChaineCourante].TSID) uFlags = MF_GRAYED; - AppendMenuA(chaines, uFlags, i, tab); + AppendMenuA(chaines, uFlags, IDM_CHAINES_BASE+i, tab); // Ajoute l'icône de la chaîne HBITMAP hImage = canal.ChargeIcone(); if (hImage) { - MENUITEMINFO mnu; - mnu.cbSize = sizeof(MENUITEMINFO); - mnu.fMask = MIIM_BITMAP; - mnu.hbmpItem = hImage; - SetMenuItemInfo(chaines, (UINT)i, FALSE, &mnu); + MENUITEMINFO mnu = { + sizeof(mnu), // cbSize + MIIM_BITMAP, // fMask + 0, 0, 0, // fType, fState, wID + NULL, NULL, NULL, // hSubMenu, hbmpChecked, hbmpUnchecked + 0, NULL, 0, // dwItemData, dwTypeData, cch + hImage // hbmpItem + }; + + SetMenuItemInfo(chaines, IDM_CHAINES_BASE+i, FALSE, &mnu); } } - InsertMenu(hMenu, position_menu, MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT_PTR)chaines, nom_menu); + InsertMenu(hMenu, MENU_CHAINES_POSITION/*position_menu*/, + MF_BYPOSITION | MF_POPUP | MF_STRING, (UINT_PTR)chaines, nom_menu); } static void update_aspect_menus(HMENU hMenu) @@ -620,13 +642,17 @@ for (int i=0; i=0 && wID<=100) { + // Items de menu créés dynamiquement + + if (wID>=IDM_CHAINES_BASE && wID=500 && wID<600) { + zappe_index(wID-IDM_CHAINES_BASE); + } else if (wID>=IDM_PISTES_BASE && wID=0) { + int grab = getFreeGrabber(); + if (grab>=0) { NomFichierAvecDate nom_fichier(video_dir, "Transport Stream", "ts"); ISampleGrabberCB * pCallback = new CSampleGrabber(nom_fichier.Create()); - enregistrements_actuels[grab].start(-2, pCallback); + enregistrements_actuels[grab].start(STREAM_PSEUDO_INDEX, pCallback); } return grab; } Modified: trunk/record.h =================================================================== --- trunk/record.h 2007-11-17 21:25:07 UTC (rev 102) +++ trunk/record.h 2007-12-04 23:15:16 UTC (rev 103) @@ -94,6 +94,9 @@ int start_record_ps(int ixChaine, int ixSon); #if USE_RECORD_STREAM +#define STREAM_PSEUDO_SID 0xffff // Pseudo SID pour l'enregistrement du multiplex +#define STREAM_PSEUDO_INDEX -2 // Pseudo index de chaîne pour l'enregistrement du multiplex + // Démarrage d'un enregistrement global du multiplex. // Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. // TODO : ajouter un paramètre (par ex. ixChaine) pour que soit préalablement syntonisé le multiplex Modified: trunk/recprog.cpp =================================================================== --- trunk/recprog.cpp 2007-11-17 21:25:07 UTC (rev 102) +++ trunk/recprog.cpp 2007-12-04 23:15:16 UTC (rev 103) @@ -35,6 +35,9 @@ #include #include +// Type liste de tâches à supprimer : +typedef std::vector ListeTaches; + // Délai de grâce pour l'arrêt après enregistrement : si, à l'arrêt d'un enregistrement, un // autre enregistrement est programmé dans un délai plus court que le délai spécifié, alors // les options d'arrêt du programme ou d'extinction de l'ordinateur sont ignorées : @@ -103,9 +106,10 @@ return false; } - const Chaine & canal = Canaux[ixChaine]; + const Chaine & canal = Canaux[ixChaine]; + int nbProgrammes = (int)Programmes.size(); - for(UINT i = 0; i < Programmes.size(); i++) { + for(int i = 0; i < nbProgrammes; i++) { const Programme & prog_verif = Programmes[i]; if(strcmp(prog_verif.nom, nom) == 0) { @@ -128,6 +132,111 @@ return true; // pas d'erreur } +// Ajout de cette programmation aux tâches programmées (retourne 'false' si échec) +bool Programme::ajouterTacheProgrammee(LPCWSTR username, LPCWSTR motdepasse) const +{ + WCHAR pwszTaskName[256]; + WORD piNewTrigger; + TASK_TRIGGER Trig; + ITaskScheduler * pITS; + ITask * pTask; + IPersistFile * pPersistFile; + ITaskTrigger * pTaskTrigger; + + SYSTEMTIME date = debut; + UINT64 fdebut; + SystemTimeToFileTime(&date, (LPFILETIME)&fdebut); + fdebut = fdebut - (UINT64)600000000; + FileTimeToSystemTime((LPFILETIME)&fdebut, &date); + + // Remplace les caracteres incorrects : + NomProtege nom_tache(nom, '_'); + + // On convertit le nom de la tâche en Unicode : + nom_tache.ToWide(pwszTaskName, _countof(pwszTaskName)); + + // Accède au planificateur pour supprimer la tâche si elle existe + HRESULT hr = CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, (void**)&pITS); + if (FAILED(hr)) { + myprintf(L"Erreur init task scheduler\n"); + return false; + } + // Supprime la tâche + pITS->Delete(pwszTaskName); + // Crée la nouvelle tâche + hr = pITS->NewWorkItem(pwszTaskName, CLSID_CTask, IID_ITask, (IUnknown**)&pTask); + + pITS->Release(); + + wchar_t path[MAX_PATH]; + + // Définition de la tâche + GetModuleFileName(NULL, path, _countof(path)); + pTask->SetApplicationName(path); + pTask->SetParameters(L"-minimize"); + GetCurrentDirectory(_countof(path), path); + pTask->SetWorkingDirectory(path); + hr=pTask->SetAccountInformation(username, motdepasse); + pTask->SetFlags(TASK_FLAG_DELETE_WHEN_DONE | TASK_FLAG_SYSTEM_REQUIRED); + pTask->SetMaxRunTime(INFINITE); + pTask->CreateTrigger(&piNewTrigger, &pTaskTrigger); + + ZeroMemory(&Trig, sizeof(TASK_TRIGGER)); + Trig.cbTriggerSize = sizeof(TASK_TRIGGER); + Trig.wBeginDay = date.wDay; + Trig.wBeginMonth = date.wMonth; + Trig.wBeginYear = date.wYear; + Trig.wStartHour = date.wHour; + Trig.wStartMinute = date.wMinute; + + bool repete = false; + for (int i=0; i<7; i++) + if (repetition[i]) { + repete = true; + break; + } + + if (repete) { + // On a demandé à ce que la tâche soit répétée + Trig.TriggerType = TASK_TIME_TRIGGER_WEEKLY; + + WEEKLY jours; + jours.WeeksInterval = 1; // Répète toutes les semaines + jours.rgfDaysOfTheWeek = 0; + + if (repetition[1]) + jours.rgfDaysOfTheWeek += TASK_MONDAY; + if (repetition[2]) + jours.rgfDaysOfTheWeek += TASK_TUESDAY; + if (repetition[3]) + jours.rgfDaysOfTheWeek += TASK_WEDNESDAY; + if (repetition[4]) + jours.rgfDaysOfTheWeek += TASK_THURSDAY; + if (repetition[5]) + jours.rgfDaysOfTheWeek += TASK_FRIDAY; + if (repetition[6]) + jours.rgfDaysOfTheWeek += TASK_SATURDAY; + if (repetition[0]) + jours.rgfDaysOfTheWeek += TASK_SUNDAY; + + Trig.Type.Weekly = jours; + } else { + // Pas de répétition de la tâche + Trig.TriggerType = TASK_TIME_TRIGGER_ONCE; + } + + pTaskTrigger->SetTrigger(&Trig); + + pTask->QueryInterface(IID_IPersistFile, (void **)&pPersistFile); + pPersistFile->Save(NULL, TRUE); + pPersistFile->Release(); + + pTaskTrigger->Release(); + pTask->Release(); + + return true; +} + /** * Traitement du chargement de la liste des programmes au démarrage, et purge * des enregistrements périmés : @@ -155,9 +264,10 @@ bool trouve = false; next_record = 0; SYSTEMTIME time; + int nbProgrammes = (int)Programmes.size(); GetLocalTime(&time); // On recherche le premier programme à enregistrer - for(int i = 0; i < (int)Programmes.size(); i++) { + for(int i=0; iDelete(pwszTaskName); - // Crée la nouvelle tâche - hr = pITS->NewWorkItem(pwszTaskName, CLSID_CTask, IID_ITask, (IUnknown**)&pTask); - - pITS->Release(); - - wchar_t path[MAX_PATH]; - - // Définition de la tâche - GetModuleFileName(NULL, path, _countof(path)); - pTask->SetApplicationName(path); - pTask->SetParameters(L"-minimize"); - GetCurrentDirectory(_countof(path), path); - pTask->SetWorkingDirectory(path); - hr=pTask->SetAccountInformation(username, mdp); - pTask->SetFlags(TASK_FLAG_DELETE_WHEN_DONE | TASK_FLAG_SYSTEM_REQUIRED); - pTask->SetMaxRunTime(INFINITE); - pTask->CreateTrigger(&piNewTrigger, &pTaskTrigger); - - ZeroMemory(&Trig, sizeof(TASK_TRIGGER)); - Trig.cbTriggerSize = sizeof(TASK_TRIGGER); - Trig.wBeginDay = date.wDay; - Trig.wBeginMonth = date.wMonth; - Trig.wBeginYear = date.wYear; - Trig.wStartHour = date.wHour; - Trig.wStartMinute = date.wMinute; - - bool repetition = false; - for (int i=0; i<7; i++) - if (prog.repetition[i]) { - repetition = true; - break; - } - - if (repetition) { - // On a demandé à ce que la tâche soit répétée - Trig.TriggerType = TASK_TIME_TRIGGER_WEEKLY; - - WEEKLY jours; - jours.WeeksInterval = 1; // Répète toutes les semaines - jours.rgfDaysOfTheWeek = 0; - - if (prog.repetition[1]) - jours.rgfDaysOfTheWeek += TASK_MONDAY; - if (prog.repetition[2]) - jours.rgfDaysOfTheWeek += TASK_TUESDAY; - if (prog.repetition[3]) - jours.rgfDaysOfTheWeek += TASK_WEDNESDAY; - if (prog.repetition[4]) - jours.rgfDaysOfTheWeek += TASK_THURSDAY; - if (prog.repetition[5]) - jours.rgfDaysOfTheWeek += TASK_FRIDAY; - if (prog.repetition[6]) - jours.rgfDaysOfTheWeek += TASK_SATURDAY; - if (prog.repetition[0]) - jours.rgfDaysOfTheWeek += TASK_SUNDAY; - - Trig.Type.Weekly = jours; - } else { - // Pas de répétition de la tâche - Trig.TriggerType = TASK_TIME_TRIGGER_ONCE; - } - - pTaskTrigger->SetTrigger(&Trig); - - pTask->QueryInterface(IID_IPersistFile, (void **)&pPersistFile); - pPersistFile->Save(NULL, TRUE); - pPersistFile->Release(); - - pTaskTrigger->Release(); - pTask->Release(); - prog.tache = planif_avec; } } // On efface le mot de passe (inutile maintenant que tous les programmes sont sauvegardés) SecureZeroMemory(mdp, sizeof(mdp)); - wcscpy(mdp, L""); } return 0; } -static int supprimer_taches(std::vector &taches_supprimer) { - for (size_t i=0; iDelete(pwszTaskName); - // Supprime la tâche - pITS->Delete(pwszTaskName); + pITS->Release(); + return 0; +} - pITS->Release(); +static int supprimer_taches(ListeTaches & taches_supprimer) { + for (size_t i=0; i(prog.nom); - SendDlgItemMessage(hdlg, lvID, LVM_INSERTITEMA, 0, (LPARAM)&item); + swprintf_s(buffer, _countof(buffer), L"%S", prog.nom); + ListView_InsertItem(hListItem, &item); item.iSubItem = 1; - sprintf_s(buffer, _countof(buffer), "%s", Canaux[ixChaine].nom); - item.pszText = buffer; - SendDlgItemMessage(hdlg, lvID, LVM_SETITEMA, 0, (LPARAM)&item); + swprintf_s(buffer, _countof(buffer), L"%S", Canaux[ixChaine].nom); + ListView_SetItem(hListItem, &item); item.iSubItem = 2; - sprintf_s(buffer, _countof(buffer), "%02i:%02i:%02i %02i/%02i/%04i", - prog.debut.wHour, prog.debut.wMinute, prog.debut.wSecond, - prog.debut.wDay, prog.debut.wMonth, prog.debut.wYear); - item.pszText = buffer; - SendDlgItemMessage(hdlg, lvID, LVM_SETITEMA, 0, (LPARAM)&item); + time2str(prog.debut, buffer, _countof(buffer)); + ListView_SetItem(hListItem, &item); item.iSubItem = 3; - sprintf_s(buffer, _countof(buffer), "%02i:%02i:%02i %02i/%02i/%04i", - prog.fin.wHour, prog.fin.wMinute, prog.fin.wSecond, - prog.fin.wDay, prog.fin.wMonth, prog.fin.wYear); - item.pszText = buffer; - SendDlgItemMessage(hdlg, lvID, LVM_SETITEMA, 0, (LPARAM)&item); + time2str(prog.fin, buffer, _countof(buffer)); + ListView_SetItem(hListItem, &item); } } @@ -475,58 +502,71 @@ * Mise à jour des cases à cocher correspondant à l'action "après enregistrement" * selon le contenu de la variable 'after'. **/ -static void SetCtlAfter(HWND hDlg, int powerID, int quitID, ApresEnregistrement after) { - CheckDlgButton(hDlg, powerID, (after==apr_eteindrePC) ? BST_CHECKED : BST_UNCHECKED); - CheckDlgButton(hDlg, quitID, (after==apr_rien) ? BST_UNCHECKED : BST_CHECKED); +static void SetCtlAfter(HWND hDlg, int powerID, int quitID, ApresEnregistrement after) +{ + CheckDlgButton(hDlg, powerID, after>=apr_eteindrePC); + CheckDlgButton(hDlg, quitID, after>=apr_quitter); } /** - * Récupère les données de l'enregistrement dans la fenêtre, - * et l'ajoute aux tâches programmées. Demande la suppression de la tâche plannifiée si besoin est... + * Récupère les données de l'enregistrement dans la fenêtre. + * Retourne 'true' si ces données sont valides, 'false' sinon. **/ -static int add_programme(HWND hwndDlg, int sel, std::vector&taches_supprimer) +static bool dlgToProg(HWND hDlg, Programme & prog) { char buffer[256]; int i; - Programme prog; - ZeroMemory(&prog, sizeof(Programme)); - (*(WORD *)&buffer[0]) = _countof(buffer)-1; - UINT lsiz = (UINT)SendDlgItemMessageA(hwndDlg, IDC_NAME, EM_GETLINE, 0, (LPARAM)buffer); - buffer[lsiz] = 0; + i = (int)SendDlgItemMessageA(hDlg, IDC_NAME, EM_GETLINE, 0, (LPARAM)buffer); + buffer[i] = 0; strcpy_s(prog.nom, _countof(prog.nom), buffer); - int ixChaine = (int)SendDlgItemMessage(hwndDlg, IDC_CHANNEL, CB_GETCURSEL, 0, 0); + int ixChaine = (int)SendDlgItemMessage(hDlg, IDC_CHANNEL, CB_GETCURSEL, 0, 0); if (!ixChaine_ok(ixChaine)) - return 1; + return false; const Chaine & canal = Canaux[ixChaine]; prog.numeroChaine = canal.numeroChaine; prog.sidChaine = canal.SID; - GetCtlTimeDate(hwndDlg, IDC_DATE_START, IDC_TIME_START, prog.debut); - GetCtlTimeDate(hwndDlg, IDC_DATE_END, IDC_TIME_END, prog.fin); + GetCtlTimeDate(hDlg, IDC_DATE_START, IDC_TIME_START, prog.debut); + GetCtlTimeDate(hDlg, IDC_DATE_END, IDC_TIME_END, prog.fin); - prog.methode = (MethodeEnregistrement)SendDlgItemMessage(hwndDlg, IDC_METHOD, CB_GETCURSEL, 0, 0); - prog.audio = (AudioMode)SendDlgItemMessage(hwndDlg, IDC_AUDIO, CB_GETCURSEL, 0, 0); - prog.after = GetCtlAfter(hwndDlg, IDC_AFTER_POWEROFF, IDC_AFTER); + prog.methode = (MethodeEnregistrement)SendDlgItemMessage(hDlg, IDC_METHOD, CB_GETCURSEL, 0, 0); + prog.audio = (AudioMode)SendDlgItemMessage(hDlg, IDC_AUDIO, CB_GETCURSEL, 0, 0); + prog.after = GetCtlAfter(hDlg, IDC_AFTER_POWEROFF, IDC_AFTER); // Ajoute l'indicatif pour la répétition for (i=0; i<7; i++) { - if (IsDlgButtonChecked(hwndDlg, IDC_REP_BASE+i) == BST_CHECKED) + if (IsDlgButtonChecked(hDlg, IDC_REP_BASE+i) == BST_CHECKED) prog.repetition[i] = true; } - if (IsDlgButtonChecked(hwndDlg, IDC_TACHE) == BST_CHECKED) { + if (IsDlgButtonChecked(hDlg, IDC_TACHE) == BST_CHECKED) { prog.tache = planif_ajout; } else { prog.tache = planif_sans; } - if (!prog.verifie()) + return prog.verifie(); +} + +/** + * Récupère les données de l'enregistrement dans la fenêtre, + * et l'ajoute aux tâches programmées. Demande la suppression de la tâche planifiée si besoin est... + **/ +static int add_programme(HWND hwndDlg, int sel, ListeTaches & taches_supprimer) +{ + int i; + + Programme prog; + ZeroMemory(&prog, sizeof(Programme)); + + // Récupération des données de l'enregistrement depuis la boîte de dialogue + if (!dlgToProg(hwndDlg, prog)) return 1; // On recherche la tâche que l'on a modifiée, pour la supprimer en premier @@ -539,8 +579,10 @@ } if (i == nbtaches) { // On ajoute à la liste des tâches devant être supprimées - char *tache = new char[strlen(prog.nom)+1]; - strcpy_s(tache, strlen(prog.nom)+1, prog.nom); + size_t sizNom = strlen(prog.nom)+1; + char *tache = new char[sizNom]; + + strcpy_s(tache, sizNom, prog.nom); taches_supprimer.push_back(tache); } @@ -555,7 +597,8 @@ int sel = -1; int n = (int)SendDlgItemMessage(hwndDlg, IDC_LIST_PROGRAMMES, LVM_GETITEMCOUNT, 0, 0); int i; - static std::vector taches_supprimer; + static HWND hListItem = NULL; + static ListeTaches taches_supprimer; for(i = 0; i < n; i++) { if(SendDlgItemMessage(hwndDlg, IDC_LIST_PROGRAMMES, LVM_GETITEMSTATE, i, LVIS_SELECTED) & LVIS_SELECTED) @@ -566,7 +609,9 @@ case WM_INITDIALOG: { - SendDlgItemMessage(hwndDlg, IDC_LIST_PROGRAMMES, LVM_SETEXTENDEDLISTVIEWSTYLE, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); + hListItem = GetDlgItem(hwndDlg, IDC_LIST_PROGRAMMES); + ListView_SetExtendedListViewStyleEx(hListItem, + LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); static const struct Tab_cols { LPCWSTR nom; @@ -588,10 +633,10 @@ // le reste à zéro (implicite) }; - SendDlgItemMessage(hwndDlg, IDC_LIST_PROGRAMMES, LVM_INSERTCOLUMN, i, (LPARAM)&colonne); + ListView_InsertColumn(hListItem, i, &colonne); } - remplit_liste_programmes(hwndDlg, IDC_LIST_PROGRAMMES); + remplit_liste_programmes(hListItem); SYSTEMTIME time; @@ -633,16 +678,15 @@ case WM_NOTIFY: switch (wParam) { case IDC_LIST_PROGRAMMES: - if (sel >= 0 && ((LPNMHDR)lParam)->code == NM_CLICK) { + if (sel >= 0 && _ncode_(lParam)==NM_CLICK) { const Programme & prog = Programmes[sel]; - SendDlgItemMessageA(hwndDlg, IDC_NAME, WM_SETTEXT, 0, (LPARAM)prog.nom); - int ixChaine = trouve_chaine_par_sid(prog.sidChaine); if (ixChaine_ok(ixChaine)) { SendDlgItemMessage(hwndDlg, IDC_CHANNEL, CB_SETCURSEL, ixChaine, 0); + SendDlgItemMessageA(hwndDlg, IDC_NAME, WM_SETTEXT, 0, (LPARAM)prog.nom); SetCtlTimeDate(hwndDlg, IDC_DATE_START, IDC_TIME_START, prog.debut); SetCtlTimeDate(hwndDlg, IDC_DATE_END, IDC_TIME_END, prog.fin); @@ -666,7 +710,7 @@ break; case IDC_DATE_START: case IDC_TIME_START: - if (((LPNMHDR)lParam)->code == DTN_DATETIMECHANGE) { + if (_ncode_(lParam)==DTN_DATETIMECHANGE) { SYSTEMTIME debut; SYSTEMTIME fin; @@ -678,7 +722,7 @@ break; case IDC_DATE_END: case IDC_TIME_END: - if (((LPNMHDR)lParam)->code == DTN_DATETIMECHANGE) { + if (_ncode_(lParam)==DTN_DATETIMECHANGE) { SYSTEMTIME debut; SYSTEMTIME fin; @@ -707,8 +751,8 @@ case IDC_REMOVE: - if(sel != -1) { - SendDlgItemMessage(hwndDlg, IDC_LIST_PROGRAMMES, LVM_DELETEITEM, sel, 0); + if (sel != -1) { + ListView_DeleteItem(hListItem, sel); Programme &prog = Programmes[sel]; if (prog.tache != planif_sans) { @@ -732,7 +776,8 @@ return FALSE; case IDC_ADD: { - int n = (int)SendDlgItemMessage(hwndDlg, IDC_LIST_PROGRAMMES, LVM_GETITEMCOUNT, 0, 0); + int n = ListView_GetItemCount(hListItem); + add_programme(hwndDlg, (sel + n + 1) % (n + 1), taches_supprimer); return TRUE; } @@ -776,21 +821,13 @@ ShowCursor(FALSE); // Rétablit la disparition temporisée du curseur return FALSE; - case WM_APP: { + case WM_APP: // Reconstruction de la liste des enregistrements programmés après changement : - LVITEM item = { - 0, // mask - 0, // iItem - 0, // iSubItem - LVIS_SELECTED, // state - LVIS_SELECTED // stateMask - }; + ListView_DeleteAllItems(hListItem); + remplit_liste_programmes(hListItem); + ListView_SetItemState(hListItem, sel, LVIS_SELECTED, LVIS_SELECTED); + return TRUE; - SendDlgItemMessage(hwndDlg, IDC_LIST_PROGRAMMES, LVM_DELETEALLITEMS, 0, 0); - remplit_liste_programmes(hwndDlg, IDC_LIST_PROGRAMMES); - SendDlgItemMessage(hwndDlg, IDC_LIST_PROGRAMMES, LVM_SETITEMSTATE, sel, (LPARAM)&item); - return TRUE; } - default: return FALSE; } Modified: trunk/recprog.h =================================================================== --- trunk/recprog.h 2007-11-17 21:25:07 UTC (rev 102) +++ trunk/recprog.h 2007-12-04 23:15:16 UTC (rev 103) @@ -70,8 +70,12 @@ bool repetition[7]; bool planifie; // Indique si un timer a été lancé pour ce programme // - // Méthode : vérification de la validité de la programmation. + // Méthodes : + // Vérification de la validité de la programmation bool verifie() const; + // + // Ajout de cette programmation aux tâches programmées (retourne 'false' si échec) + bool ajouterTacheProgrammee(LPCWSTR username, LPCWSTR motdepasse) const; }; extern std::vector Programmes; Modified: trunk/resource.h =================================================================== --- trunk/resource.h 2007-11-17 21:25:07 UTC (rev 102) +++ trunk/resource.h 2007-12-04 23:15:16 UTC (rev 103) @@ -99,6 +99,8 @@ #define IDC_RENUM_REST 1115 #define MY_TRAY_ICON_ID 20001 #define MY_TRAY_ICON_MESSAGE 20002 +#define IDM_CHAINES_BASE 39000 // Offset de base pour changement chaîne +#define IDM_PISTES_BASE 39500 // Offset de base pour choix bande sonore #define IDM_QUIT 40001 #define IDM_ABOUT 40002 #define IDM_CONFIG 40003 @@ -110,17 +112,11 @@ #define IDM_AC3 40009 #define IDM_DSOUNDAC3 40010 //#define IDM_CHANNELS 40011 - -#if defined(USE_RECORD_STREAM) - #define IDM_RECORD_STREAM 40012 -#endif - -#define IDM_SECOND_AUDIO 40013 #define IDM_SCREENSHOT 40014 #define IDM_FULLSCREEN 40015 #define IDM_AC3_AUDIO 40016 -#define IDM_STOP_RECORD 40017 #define IDM_FIRST_AUDIO 40018 +#define IDM_SECOND_AUDIO 40019 #define IDM_MUTE 40020 #define IDM_MINIMIZE 40021 #define IDM_RESTORE 40022 @@ -137,30 +133,37 @@ #define IDM_NORMAL_PRIORITY 40037 #define IDM_BELOW_NORMAL_PRIORITY 40038 #define IDM_IDLE_PRIORITY 40039 -#define IDM_EPG 40043 -#define IDM_SIGNAL 40047 -#define IDM_RECORD_CHANNEL_TS 40049 -#define IDM_RECORD_CHANNEL_PS 40050 -#define IDM_VOL_AUG 40055 -#define IDM_VOL_DIM 40056 -#define IDM_ZOOM 40063 -#define IDM_DEZOOM 40064 -#define IDM_STRETCH 40065 -#define IDM_DEFAULT_ZOOM 40071 -#define IDM_PISTES 40100 -#define IDM_CHAINES 40101 -#define IDM_DELAYED_RECORD 40104 -#define IDM_DELAYED_STOP 40105 -#define IDM_NOAR 40106 -#define IDM_UPDATE 40107 -#define IDM_SHOW_PROGRAMME 40108 +#define IDM_EPG 40040 +#define IDM_SIGNAL 40041 +#define IDM_DELAYED_RECORD 40044 +#define IDM_RECORD_CHANNEL_TS 40045 +#define IDM_RECORD_CHANNEL_PS 40046 +#if defined(USE_RECORD_STREAM) + #define IDM_RECORD_STREAM 40047 +#endif + +#define IDM_STOP_RECORD 40048 +#define IDM_DELAYED_STOP 40049 + +#define IDM_VOL_AUG 40050 +#define IDM_VOL_DIM 40051 +#define IDM_ZOOM 40053 +#define IDM_DEZOOM 40054 +#define IDM_DEFAULT_ZOOM 40055 +#define IDM_STRETCH 40056 +#define IDM_PISTES 40060 +#define IDM_CHAINES 40061 +#define IDM_NOAR 40066 +#define IDM_UPDATE 40067 +#define IDM_SHOW_PROGRAMME 40068 + // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 133 -#define _APS_NEXT_COMMAND_VALUE 40109 +#define _APS_NEXT_COMMAND_VALUE 40069 #define _APS_NEXT_CONTROL_VALUE 1116 #define _APS_NEXT_SYMED_VALUE 121 #endif Modified: trunk/search.cpp =================================================================== --- trunk/search.cpp 2007-11-17 21:25:07 UTC (rev 102) +++ trunk/search.cpp 2007-12-04 23:15:16 UTC (rev 103) @@ -29,6 +29,32 @@ BOOL exact_match; +// Structure d'assistance à la récupération d'une chaîne de caratères depuis une variable de type +// VARIANT (permet en particulier de nettoyer la chaîne au passage, pour corriger un problème +// constaté avec certains tuners): +struct VarStr_helper +{ + wchar_t str[128]; + + VarStr_helper(const VARIANT & var); + LPCWSTR operator () () const {return str;} +}; + +// Constructeur : +VarStr_helper::VarStr_helper(const VARIANT & var) +{ + if (var.vt==VT_BSTR) { + wcscpy_s(str, _countof(str), var.bstrVal); + str[_countof(str)-1] = 0; // précaution + + int len = wcslen(str); + + while (len>0 && str[--len]<=' ') + str[len] = 0; + } else + str[0] = 0; +} + void search_filters(GUID type, GUID subtype, HWND hItm) { IFilterMapper2 * pMapper; @@ -84,12 +110,12 @@ VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName", &varName, 0); - if (SUCCEEDED(hr)) - { + if (SUCCEEDED(hr)) { // Display the name in your UI somehow. - //myprintf(L"filtre : %s\n", varName.bstrVal); + VarStr_helper vstr(varName); + //myprintf(L"filtre : %s\n", vstr); - SendMessage(hItm, CB_ADDSTRING, 0, (LPARAM)varName.bstrVal); + SendMessage(hItm, CB_ADDSTRING, 0, (LPARAM)vstr()); } VariantClear(&varName); pPropBag->Release(); @@ -159,11 +185,11 @@ VARIANT varName; VariantInit(&varName); hr = pPropBag->Read(L"FriendlyName", &varName, 0); - if (SUCCEEDED(hr)) - { + if (SUCCEEDED(hr)) { // Display the name in your UI somehow. + VarStr_helper vstr(varName); - if (wcscmp(varName.bstrVal, nom)==0) { + if (wcscmp(vstr(), nom)==0) { hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter); @@ -358,10 +384,11 @@ // To retrieve the friendly name of the filter, do the following: VARIANT varName; VariantInit(&varName); - if (SUCCEEDED(pPropBag->Read(L"FriendlyName", &varName, 0))) - { - if (wcscmp(varName.bstrVal, nom)==0) { + if (SUCCEEDED(pPropBag->Read(L"FriendlyName", &varName, 0))) { + VarStr_helper vstr(varName); + if (wcscmp(vstr(), nom)==0) { + hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter); } Modified: trunk/settings.cpp =================================================================== --- trunk/settings.cpp 2007-11-17 21:25:07 UTC (rev 102) +++ trunk/settings.cpp 2007-12-04 23:15:16 UTC (rev 103) @@ -36,8 +36,6 @@ #include -#define _cmd_ MAKEWPARAM // -> pour tenter de rendre les choses plus lisibles - // Passé via lParam aux fonction "callback" des dialogues pour leur permettre de différencier // entre le mode "property sheet" et le mode dialogue normal (ceci permet d'utiliser, dans // certains cas, le même modèle de boîte de dialogue dans une feuille de propriétés ou bien @@ -45,15 +43,14 @@ #define DIALOGMODE ((LPARAM)FALSE) #define PROPSHEETMODE ((LPARAM)TRUE) -LRESULT set_check(HWND hDlg, int nID, bool state) +inline bool set_check(HWND hDlg, int nID, bool state) { - return SendDlgItemMessage(hDlg, nID, - BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0); + return CheckDlgButton(hDlg, nID, state ? BST_CHECKED : BST_UNCHECKED)==TRUE; } -bool get_check(HWND hDlg, int nID) +inline bool get_check(HWND hDlg, int nID) { - return SendDlgItemMessage(hDlg, nID, BM_GETCHECK, 0, 0)==BST_CHECKED; + return IsDlgButtonChecked(hDlg, nID)==BST_CHECKED; } // Assistance au retour de valeur après notification dans une feuille de propriétés : @@ -178,7 +175,7 @@ return FALSE; case WM_NOTIFY: - switch (((NMHDR FAR *) lParam)->code) { + switch (_ncode_(lParam)) { case PSN_APPLY: // Application des changements if (drivers_apply(hDlg)) { MessageBox(hMainWnd, @@ -960,7 +957,7 @@ switch (uMsg) { case WM_INITDIALOG: // Initialisation de la fenêtre - SendDlgItemMessage(hDlg, IDC_RENUM_SEQ, BM_SETCHECK, WPARAM(BST_CHECKED), 0); + CheckDlgButton(hDlg, IDC_RENUM_SEQ, BST_CHECKED); return TRUE; case WM_COMMAND: @@ -970,7 +967,7 @@ case IDOK: // Clic sur le bouton OK EndDialog(hDlg, - SendDlgItemMessage(hDlg, IDC_RENUM_REST, BM_GETCHECK, 0, 0)==BST_CHECKED ? + IsDlgButtonChecked(hDlg, IDC_RENUM_REST)==BST_CHECKED ? IDC_RENUM_REST : IDC_RENUM_SEQ); return TRUE; @@ -1016,7 +1013,7 @@ switch (uMsg) { - case WM_INITDIALOG: { + case WM_INITDIALOG: if (ixChaine_ok(ixChaineCourante)) sidSauveChaineCourante = Canaux[ixChaineCourante].SID; hListItem = GetDlgItem(hDlg, IDC_CHANNEL_LIST); @@ -1036,7 +1033,7 @@ } remplit_liste_chaines(hListItem); - return TRUE; } + return TRUE; case WM_COMMAND: switch(wParam) { @@ -1132,7 +1129,7 @@ return FALSE; case WM_NOTIFY: - switch (((NMHDR FAR *) lParam)->code) { + switch (_ncode_(lParam)) { #ifdef MODIFY_CHANNELS_BY_CONTROLS case NM_CLICK: @@ -1360,7 +1357,7 @@ return FALSE; case WM_NOTIFY: - switch (((NMHDR FAR *) lParam)->code) { + switch (_ncode_(lParam)) { case PSN_APPLY: // Application des changements suspend_minimized = get_check(hDlg, IDC_SUSPEND); minimize_system_tray = get_check(hDlg, IDC_SYSTEM_TRAY); @@ -1455,7 +1452,7 @@ return FALSE; case WM_NOTIFY: - switch (((NMHDR FAR *) lParam)->code) { + switch (_ncode_(lParam)) { case PSN_APPLY: // Application des changements SendMessage(hVideoDirItem, WM_GETTEXT, _countof(video_dir), (LPARAM)video_dir); From pouchintv-svn at baysse.fr Fri Dec 14 15:17:27 2007 From: pouchintv-svn at baysse.fr (pouchintv-svn at baysse.fr) Date: Fri, 14 Dec 2007 15:17:27 +0100 (CET) Subject: [Pouchintv-dev] [PouchinTVMod] gingko | r104 - trunk Message-ID: <20071214141727.87A485F305@mail.baysse.fr> Author: gingko Date: 2007-12-14 15:17:27 +0100 (Fri, 14 Dec 2007) New Revision: 104 Modified: trunk/channels.cpp trunk/channels.h trunk/ini.h trunk/main.cpp trunk/main.h trunk/record.cpp trunk/record.h trunk/recprog.cpp trunk/recprog.h trunk/res.rc trunk/resource.h trunk/settings.cpp trunk/trayicon.cpp Log: Ajout d'un timer espa?\195?\167ant les mises ?\195?\160 jour de la position de la vid?\195?\169o (aux 100 millisecondes) lors des d?\195?\169placements de la fen?\195?\170tre, minimisant ainsi les risques de ruptures de la vid?\195?\169o caus?\195?\169es par trop de mises ?\195?\160 jour successives. Ajout de raccourcis clavier pour le dialogue des enregistrements programm?\195?\169s (P) et pour le dialogue d'arr?\195?\170t temporis?\195?\169 (S). Les fonctions d'arr?\195?\170t de l'application et d'extinction de l'ordinateur sont d?\195?\169plac?\195?\169es dans 'main.cpp', et ex?\195?\169cut?\195?\169es seulement ?\195?\160 l'issue d'un d?\195?\169lai de 2 secondes apr?\195?\168s la fin de l'enregistrement, pour permettre un arr?\195?\170t plus propre. Commentaires ajout?\195?\169s ou modifi?\195?\169s. Modified: trunk/channels.cpp =================================================================== --- trunk/channels.cpp 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/channels.cpp 2007-12-14 14:17:27 UTC (rev 104) @@ -105,6 +105,12 @@ return hImage; } +// Génération du nom affichable pour le menu +int Chaine::toMenuString(LPWSTR pstr, size_t bufSize) const +{ + return swprintf_s(pstr, bufSize, L"%4i : %S", numeroChaine, nom); +} + /** * Recherche de l'index correspondant à un numéro de chaîne 'numChaine'. * Retourne -1 si pas trouvé. @@ -136,6 +142,18 @@ } /** + * Trouver le SID correspodant à un index de chaîne. + **/ +WORD indexToSID(int ixChaine) +{ + return +#if USE_RECORD_STREAM + ixChaine==STREAM_PSEUDO_INDEX ? STREAM_PSEUDO_SID : +#endif + ixChaine_ok(ixChaine) ? Canaux[ixChaine].SID : 0; +} + +/** * Trouve le premier numéro de chaîne inutilisé, pour pouvoir l'attribuer à une nouvelle chaîne. **/ WORD nouveau_numero_chaine() Modified: trunk/channels.h =================================================================== --- trunk/channels.h 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/channels.h 2007-12-14 14:17:27 UTC (rev 104) @@ -99,6 +99,8 @@ { return numeroChaine > canal2.numeroChaine || (numeroChaine == canal2.numeroChaine && _stricmp(nom, canal2.nom) > 0); } + // Génération du nom affichable pour le menu + int toMenuString(LPWSTR pstr, size_t bufSize) const; }; // Tableau des chaînes @@ -143,6 +145,11 @@ inline bool ixChaine_ok(int ixChaine); /** + * Trouver le SID correspodant à un index de chaîne. + **/ +WORD indexToSID(int ixChaine); + +/** * Tri des chaînes par numéro. * En cas d'identité de numéro (numéro pas attribué = 0, par exemple), * les noms de chaînes sont comparés. Modified: trunk/ini.h =================================================================== --- trunk/ini.h 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/ini.h 2007-12-14 14:17:27 UTC (rev 104) @@ -36,11 +36,35 @@ int lit_chaines(); void sauve_chaines(void); + +/** + * Lecture des programmations d'enregistrement depuis un fichier. + **/ int lit_programmes(void); + +/** + * Sauvegarde des programmations d'enregistrements dans un fichier. + **/ void sauve_programmes(void); + +/** + * Sauvegarde de la configuration dans un fichier. + **/ void save_config(void); + +/** + * Lecture de la configuration depuis un fichier. + **/ void load_config(void); + +/** + * Chargement de la combo box 'hItm' à partir du fichier des noms de villes. + **/ void remplit_villes(HWND hItm); + +/** + * Vérification de la validité de la configuration du tuner TNT. + **/ bool check_config(void); // Structure pour la sauvegarde de la position des fenêtres : @@ -59,9 +83,13 @@ extern WindowPos consolePos; #endif // #if USE_CONSOLE==1 +// Priorité du processus courant selon la configuration extern DWORD configPriority; + +// État de la fenêtre principale selon la configuration extern EtatsFenetre configState; +// Nom de base du fichier définissant les villes et les canaux extern wchar_t scan_ini[]; // Structure avec constructeur, chargeant une chaîne de caractères et la protégeant @@ -117,6 +145,6 @@ LPCWSTR dword2str(const AssocElement * assocTable, DWORD value); /** - * Table d'association pour l'état des chaînes. + * Table d'associations pour nommer les états possible des chaînes. **/ extern AssocElement aChStateTable[]; Modified: trunk/main.cpp =================================================================== --- trunk/main.cpp 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/main.cpp 2007-12-14 14:17:27 UTC (rev 104) @@ -91,6 +91,9 @@ // Délai de persistance du curseur (pointeur souris) en mode plein écran : #define DUREE_CURSEUR 3000 // Valeur en millisecondes +// Intervalle minimal entre deux actualisations de la position de la vidéo : +#define DUREE_VIDEO_MOVE 100 + // Positions des sous-menus du menu principal : #define MENU_FICHIER_POSITION 0 #define MENU_FILTRES_POSITION 1 @@ -120,6 +123,8 @@ bool etirer_video = false; int zoom_ratio = 100; +bool shutdown_on_quit = false; + // Répertoire de Pouchin TV Mod wchar_t pouchindir_prog[MAX_PATH]; // Répertoire pour la sauvegarde des paramètres (Dossier App Data) @@ -321,6 +326,18 @@ } static void update_coords(void) { + static DWORD last_tick = 0; + DWORD tick = GetTickCount(); + + if (tick-last_tick < DUREE_VIDEO_MOVE) { + // Le dernier appel à cette fonction est vieux de moins que DUREE_VIDEO_MOVE : + // On ne fait rien, et on programme un timer pour rappeler la même fonction + // lorsque ce délai sera écoulé. + SetTimer(hMainWnd, TIMER_VIDEO_MOVE, DUREE_VIDEO_MOVE-(tick-last_tick), NULL); + return; + } + last_tick = tick; + RECT rcDest; // Get the window client area. GetClientRect(hMainWnd, &rcDest); @@ -371,7 +388,7 @@ { static bool _recording; - if (recording() != _recording) { + if ((recording()!=0) != _recording) { _recording = !_recording; // Change l'icone indiquant si oui ou non on enregistre en ce moment @@ -531,23 +548,23 @@ DeleteMenu(hMenu, MENU_CHAINES_POSITION/*position_menu*/, MF_BYPOSITION); HMENU chaines = CreatePopupMenu(); - bool _recording = recording(); + bool _recording = recording()!=0; int nbChaines = (int)Canaux.size(); for (int i=0; i= 0 && grab < NB_MAX_ENREG) { @@ -2186,7 +2223,7 @@ case WM_CLOSE: //if (IsMinimized(hWnd)) // ShowWindow(hWnd, SW_RESTORE); - if (recording()) { + if (recording()!=0) { // Si on enregistre affiche la fenêtre de confirmation if (DialogBox(hAppInstance, MAKEINTRESOURCE(IDD_CLOSE_CONFIRM), hWnd, ConfirmCloseProc) == 0) break; // On a appuyé sur Annuler, donc, on ne quitte pas @@ -2441,6 +2478,38 @@ } } +// Arrêt de l'ordinateur : +static bool shutdown() +{ + HANDLE hToken; + TOKEN_PRIVILEGES tkp; + + // Récupère un jetton pour l'application. + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + return false; + + // Obtient le LUID pour le privilège d'arrêt. + LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); + + tkp.PrivilegeCount = 1; // 1 privilège à obtenir + tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Obtient le privilège d'arrêt pour ce processus. + AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0); + + if (GetLastError() != ERROR_SUCCESS) + return false; + + // Arrête Windows, et force toutes les applications à s'arrêter. + if (!ExitWindowsEx(EWX_SHUTDOWN | EWX_FORCE, SHTDN_REASON_MAJOR_OPERATINGSYSTEM | + SHTDN_REASON_MINOR_UPGRADE |SHTDN_REASON_FLAG_PLANNED)) + // Erreur, on annule tout + return false; + + // Tout est OK, le système s'arrête... + return true; +} + int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // Première chose à faire: @@ -2576,6 +2645,8 @@ // Chargement et purge de la liste des programmes init_programmation(); + // Définir le timer avant l'enregistrement suivant, + // éventuellement débutant immédiatement si intervalle en cours : set_timer_record(); // Récupère la version de windows @@ -2747,6 +2818,12 @@ // On libère toutes les ressources initialisées CoUninitialize(); + if (shutdown_on_quit) { + if (!shutdown()) { + myprintf(L"Impossible d'arrêter l'ordinateur"); + } + } + // Pour témoigner qu'on est bien passés par ici en quittant : myprintf(L"On quitte !\n"); return (int)msg.wParam; Modified: trunk/main.h =================================================================== --- trunk/main.h 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/main.h 2007-12-14 14:17:27 UTC (rev 104) @@ -76,6 +76,9 @@ #endif // #if LOG_DSHOW // Liste des codes de timers définis : +#define TIMER_VIDEO_MOVE 120 +#define TIMER_QUIT 121 +#define TIMER_SHUTDOWN 122 #define TIMER_NUMBER 123 #define TIMER_BORDER 124 #define TIMER_OSD 125 Modified: trunk/record.cpp =================================================================== --- trunk/record.cpp 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/record.cpp 2007-12-14 14:17:27 UTC (rev 104) @@ -71,13 +71,15 @@ * * Renvoie 1 si on enregistre actuellement, 0 sinon **/ -bool recording() +WORD recording() { - for (int i=0; i> 1) // = 0x7fffffff, si 'long' est sur 32 bits + class Enregistrement { int indexChaine; // Numéro de la chaine enregistrée ApresEnregistrement apres; // Ce que doit faire Pouchin TV Mod une fois l'enregistrement terminé @@ -49,9 +54,14 @@ extern Enregistrement enregistrements_actuels[NB_MAX_ENREG]; -// Fonction retournant 'true' s'il y a au moins un enregistrement en cours -bool recording(); +/** + * Détermine si on enregistre actuellement + * + * Renvoie 1 si on enregistre actuellement, 0 sinon + **/ +WORD recording(); + /** * Arrêt de l'enregistrement d'index 'grab'. Retourne le code correspondant * à l'action qui avait été programmée à l'issue de cet enregistrement. @@ -76,30 +86,36 @@ **/ int getFreeGrabber(); -// Démarrage d'un enregistrement en mode TS, pour la chaîne dont l'index est égal à ixChaine -// Le tuner est supposé être déjà syntonisé sur la bonne fréquence -// Toutes les pistes audio sont enregistrées -// Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. -// TODO : retirer la restriction sur la syntonisation -// TODO : ajouter le paramètre 'ixSon' pour choisir entre l'enregistrement d'une piste audio donnée, -// ou bien toutes les pistes audio si -1 +/** + * Démarrage d'un enregistrement en mode TS, pour la chaîne dont l'index est égal à ixChaine. + * Le tuner est supposé être déjà syntonisé sur la bonne fréquence. + * Toutes les pistes audio sont enregistrées. + * Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. + * TODO : retirer la restriction sur la syntonisation + * TODO : ajouter le paramètre 'ixSon' pour choisir entre l'enregistrement d'une piste audio donnée, + * ou bien toutes les pistes audio si -1 + **/ int start_record_ts(int ixChaine); -// Démarrage d'un enregistrement en mode PS, pour la chaîne dont l'index -// est égal à 'ixChaine', avec 'ixSon' comme piste son -// Le tuner est supposé être déjà syntonisé sur la bonne fréquence -// Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. -// TODO : retirer la restriction sur la syntonisation -// TODO : faire en sorte que toutes les pistes son soient enregistrées si 'ixSon'=-1 +/** + * Démarrage d'un enregistrement en mode PS, pour la chaîne dont l'index. + * est égal à 'ixChaine', avec 'ixSon' comme piste son. + * Le tuner est supposé être déjà syntonisé sur la bonne fréquence. + * Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. + * TODO : retirer la restriction sur la syntonisation + * TODO : faire en sorte que toutes les pistes son soient enregistrées si 'ixSon'=-1 + **/ int start_record_ps(int ixChaine, int ixSon); #if USE_RECORD_STREAM #define STREAM_PSEUDO_SID 0xffff // Pseudo SID pour l'enregistrement du multiplex #define STREAM_PSEUDO_INDEX -2 // Pseudo index de chaîne pour l'enregistrement du multiplex -// Démarrage d'un enregistrement global du multiplex. -// Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. -// TODO : ajouter un paramètre (par ex. ixChaine) pour que soit préalablement syntonisé le multiplex -// correspondant à la chaîne désignée. +/** + * Démarrage d'un enregistrement global du multiplex. + * Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. + * TODO : ajouter un paramètre (par ex. ixChaine) pour que soit préalablement syntonisé le multiplex + * correspondant à la chaîne désignée. + **/ int start_record_stream(); -#endif +#endif \ No newline at end of file Modified: trunk/recprog.cpp =================================================================== --- trunk/recprog.cpp 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/recprog.cpp 2007-12-14 14:17:27 UTC (rev 104) @@ -41,8 +41,13 @@ // Délai de grâce pour l'arrêt après enregistrement : si, à l'arrêt d'un enregistrement, un // autre enregistrement est programmé dans un délai plus court que le délai spécifié, alors // les options d'arrêt du programme ou d'extinction de l'ordinateur sont ignorées : -#define DUREE_GRACE_QUIT 15 // Valeur en minutes +#define DUREE_GRACE_QUIT 15 // Valeur en minutes +// En cas d'arrêt programmé de l'application et/ou de l'ordinateur à l'issue d'un enregistrement, +// le délai suivant est appliqué avant l'exécution effective de l'arrêt, afin de garantir une +// terminaison propre de l'enregistrement : +#define DUREE_QUIT 2000 // Valeur en millisecondes + std::vector Programmes; int next_record = -1; @@ -66,12 +71,21 @@ /** * Fonction qui retourne la différence time1 - time2, convertie en millisecondes. **/ -static long DiffTime(SYSTEMTIME time1, SYSTEMTIME time2) +long DiffTime(SYSTEMTIME time1, SYSTEMTIME time2) { INT64 Utime1, Utime2; SystemTimeToFileTime(&time1, (LPFILETIME)&Utime1); SystemTimeToFileTime(&time2, (LPFILETIME)&Utime2); - return long((Utime1 - Utime2) / 10000); + + // Plafonner la différence à DUREE_INFINIE-1 + INT64 diff = (Utime1 - Utime2) / 10000; + + if (diff > DUREE_INFINIE-1) + return DUREE_INFINIE-1; + if (diff < -(DUREE_INFINIE-1)) + return -(DUREE_INFINIE-1); + + return long(diff); } /** @@ -962,7 +976,7 @@ // bien l'enregistrement en cours doit concerner le même multiplex. long frequence_courante = Canaux[ixChaineCourante].frequence; - if (recording() && canal.frequence != frequence_courante) + if (recording()!=0 && canal.frequence != frequence_courante) return -1; // si on ne peut pas enregistrer if (canal.frequence != frequence_courante) { @@ -1054,38 +1068,6 @@ return grab; } -// Arrêt de l'ordinateur : -static bool shutdown() -{ - HANDLE hToken; - TOKEN_PRIVILEGES tkp; - - // Récupère un jetton pour l'application. - if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) - return false; - - // Obtient le LUID pour le privilège d'arrêt. - LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid); - - tkp.PrivilegeCount = 1; // 1 privilège à obtenir - tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - // Obtient le privilège d'arrêt pour ce processus. - AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0); - - if (GetLastError() != ERROR_SUCCESS) - return false; - - // Arrête Windows, et force toutes les applications à s'arrêter. - if (!ExitWindowsEx(EWX_SHUTDOWN | EWX_FORCE, SHTDN_REASON_MAJOR_OPERATINGSYSTEM | - SHTDN_REASON_MINOR_UPGRADE |SHTDN_REASON_FLAG_PLANNED)) - // Erreur, on annule tout - return false; - - // Tout est OK, le système s'arrête... - return true; -} - void do_record_stop(int grab) { ApresEnregistrement after = stop_record(grab); @@ -1102,22 +1084,22 @@ } myprintf(L"diff : %i mS\n", diff); - if (!recording() && (diff >= DUREE_GRACE_QUIT*60*1000 || diff == -1)) { + if (!recording()!=0 && (diff >= DUREE_GRACE_QUIT*60*1000 || diff == -1)) { // On entre ici si aucun (autre) enregistrement n'est en cours, et si aucun // enregistrement n'est programmé pour débuter dans un délai défini par // la constante DUREE_GRACE_QUIT. - // Arrêt de pouchinTV ? - if (after == apr_quitter) { - if (IsMinimized(hMainWnd)) - ShowWindow(hMainWnd, SW_RESTORE); - DestroyWindow(hMainWnd); + // Traiter l'arrêt du programme et/ou de l'ordinateur s'il y a lieu : + switch (after) { + + case apr_quitter: + // Arrêt de pouchinTV + SetTimer(hMainWnd, TIMER_QUIT, DUREE_QUIT, NULL); + break; + + case apr_eteindrePC: + // Arrêt de l'ordinateur + SetTimer(hMainWnd, TIMER_SHUTDOWN, DUREE_QUIT, NULL); } - // Arrêt de l'ordinateur - if (after == apr_eteindrePC) { - if (!shutdown()) { - erreur(L"Impossible d'arrêter l'ordinateur"); - } - } } } Modified: trunk/recprog.h =================================================================== --- trunk/recprog.h 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/recprog.h 2007-12-14 14:17:27 UTC (rev 104) @@ -51,8 +51,8 @@ enum PlanificateurTache { planif_sans, // 0: non planifiée - planif_avec, // 1: planifiée - planif_ajout // 2: a ajouter au planificateur lors de la validation + planif_avec, // 1: planifiée (ajout effectué) + planif_ajout // 2: planifiée (à ajouter au planificateur lors de la validation) }; struct Programme { Modified: trunk/res.rc =================================================================== --- trunk/res.rc 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/res.rc 2007-12-14 14:17:27 UTC (rev 104) @@ -67,7 +67,6 @@ POPUP "&Fichier" BEGIN MENUITEM "&Configuration...", IDM_CONFIG - //MENUITEM "&Recherche de chaînes...", IDM_CHANNELS MENUITEM SEPARATOR MENUITEM "&Quitter", IDM_QUIT END @@ -100,7 +99,7 @@ MENUITEM "&Pistes", IDM_PISTES POPUP "&Captures" BEGIN - MENUITEM "Enregistrements p&rogrammés...", IDM_DELAYED_RECORD + MENUITEM "Enregistrements p&rogrammés...\tP", IDM_DELAYED_RECORD MENUITEM "Enregistrer la chaîne en &PS\tCtrl+Espace", IDM_RECORD_CHANNEL_PS MENUITEM "Enregistrer la chaîne en &TS\tCtrl+T", IDM_RECORD_CHANNEL_TS #if defined(USE_RECORD_STREAM) @@ -108,7 +107,7 @@ #endif MENUITEM SEPARATOR MENUITEM "&Stopper l'enregistrement\tCtrl+Entrée", IDM_STOP_RECORD - MENUITEM "Stopper l'enregistrement &dans...", IDM_DELAYED_STOP + MENUITEM "Stopper l'enregistrement &dans...\tS", IDM_DELAYED_STOP MENUITEM SEPARATOR MENUITEM "&Capture d'écran\tCtrl+X", IDM_SCREENSHOT END @@ -168,7 +167,7 @@ IDD_OPTIONS DIALOGEX 0, 0, 290, 214 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Dialog" +CAPTION "Options" FONT 8, "MS Shell Dlg", 0, 0, 0x1 BEGIN GROUPBOX "Mode minimisé",IDC_STATIC,7,7,276,50 @@ -183,7 +182,7 @@ IDD_FOLDERS DIALOGEX 0, 0, 290, 214 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Dialog" +CAPTION "Répertoires" FONT 8, "MS Shell Dlg", 0, 0, 0x0 BEGIN LTEXT "Répertoire des captures vidéo :",IDC_STATIC,7,39,145,8 @@ -339,16 +338,7 @@ CAPTION "Chaînes" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN -#if defined(MODIFY_CHANNELS_BY_CONTROLS) - CONTROL "",IDC_CHANNEL_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_AUTOARRANGE | LVS_EDITLABELS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,275,158 - GROUPBOX "Modifier :",IDC_STATIC_MODIFY,7,167,275,25,WS_DISABLED - RTEXT "N°",IDC_STATIC_NUMBER,8,179,12,8,WS_DISABLED - EDITTEXT IDC_NUMBER,23,176,30,14,ES_CENTER | ES_AUTOHSCROLL | ES_NUMBER | WS_DISABLED - CONTROL "Active",IDC_ACTIVE,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,169,178,33,10 - CONTROL "Préférée",IDC_PREFERRED,"Button",BS_AUTOCHECKBOX | WS_DISABLED | WS_TABSTOP,235,178,40,10 -#else CONTROL "",IDC_CHANNEL_LIST,"SysListView32",LVS_REPORT | LVS_SHOWSELALWAYS | LVS_AUTOARRANGE | LVS_EDITLABELS | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,275,182 -#endif PUSHBUTTON "Renuméroter ...",IDC_RENUMBER,7,193,59,14 PUSHBUTTON "Recherche de chaînes ...",IDC_SCAN,169,193,113,14,BS_MULTILINE END @@ -496,8 +486,10 @@ "G", IDM_EPG, VIRTKEY, NOINVERT VK_SPACE, IDM_FULLSCREEN, VIRTKEY, NOINVERT "M", IDM_MUTE, VIRTKEY, NOINVERT + "P", IDM_DELAYED_RECORD, VIRTKEY, NOINVERT VK_SPACE, IDM_RECORD_CHANNEL_PS, VIRTKEY, CONTROL, NOINVERT "T", IDM_RECORD_CHANNEL_TS, VIRTKEY, CONTROL, NOINVERT + "S", IDM_DELAYED_STOP, VIRTKEY, NOINVERT #if defined(USE_RECORD_STREAM) "M", IDM_RECORD_STREAM, VIRTKEY, CONTROL, NOINVERT #endif Modified: trunk/resource.h =================================================================== --- trunk/resource.h 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/resource.h 2007-12-14 14:17:27 UTC (rev 104) @@ -87,16 +87,9 @@ #define IDC_REP_SAMEDI 1106 #define IDC_SCAN 1107 -#if defined(MODIFY_CHANNELS_BY_CONTROLS) - #define IDC_STATIC_MODIFY 1108 - #define IDC_ACTIVE 1109 - #define IDC_PREFERRED 1110 - #define IDC_STATIC_NUMBER 1111 - #define IDC_NUMBER 1112 -#endif -#define IDC_RENUMBER 1113 -#define IDC_RENUM_SEQ 1114 -#define IDC_RENUM_REST 1115 +#define IDC_RENUMBER 1108 +#define IDC_RENUM_SEQ 1109 +#define IDC_RENUM_REST 1110 #define MY_TRAY_ICON_ID 20001 #define MY_TRAY_ICON_MESSAGE 20002 #define IDM_CHAINES_BASE 39000 // Offset de base pour changement chaîne Modified: trunk/settings.cpp =================================================================== --- trunk/settings.cpp 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/settings.cpp 2007-12-14 14:17:27 UTC (rev 104) @@ -241,7 +241,7 @@ hInfoText = GetDlgItem(hDlg, IDC_SCAN_INFO); // Champ d'informations hProgBar = GetDlgItem(hDlg, IDC_SCAN_PROGRESS); // Barre de progression // - sidChaine = ixChaine_ok(ixChaineCourante) ? Canaux[ixChaineCourante].SID : 0; + sidChaine = indexToSID(ixChaineCourante); remplit_combo_ville(hComboItem, nomVille); SendMessage(hComboItem, CB_SELECTSTRING, (WPARAM)-1, (LPARAM)nomVille); // @@ -439,7 +439,7 @@ CHAN_COL_STATE, // iSubItem 0, 0, // state, stateMask LPWSTR(dword2str(aChStateTable, etat)) // pszText - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; ListView_SetItem(hListItem, &item); @@ -456,7 +456,7 @@ buffer, // pszText 0, 0, // cchTextMax, iImage canal.SID // lParam - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; swprintf_s(buffer, _countof(buffer), L"%u", canal.numeroChaine); @@ -628,7 +628,7 @@ 0, // iSubItem 0, 0, // state, stateMask L"", // pszText - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; iItemEmpty = ListView_InsertItem(hListItem, &item); @@ -750,7 +750,7 @@ // Trouver l'item sous la souris : LVHITTESTINFO lvhti = { {x, y} - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; ClientToScreen(hDlg, &lvhti.pt); @@ -846,7 +846,7 @@ 0, // iSubItem 0, // state, LVIS_SELECTED // stateMask - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; ListView_GetItem(hListItem, &lvi); @@ -869,17 +869,6 @@ myprintf(L"ChannelsLVDrag détruit\n"); } -#ifdef MODIFY_CHANNELS_BY_CONTROLS -static void EnableModifications(HWND hDlg, BOOL bEnable) -{ - EnableWindow(GetDlgItem(hDlg, IDC_STATIC_MODIFY), bEnable); - EnableWindow(GetDlgItem(hDlg, IDC_ACTIVE), bEnable); - EnableWindow(GetDlgItem(hDlg, IDC_PREFERRED), bEnable); - EnableWindow(GetDlgItem(hDlg, IDC_STATIC_NUMBER), bEnable); - EnableWindow(GetDlgItem(hDlg, IDC_NUMBER), bEnable); -} -#endif - // Application d'un changement de numéro de chaîne : static void channel_number_changed(HWND hListItem, int iItem, LPCWSTR str_newnum) { @@ -894,7 +883,7 @@ LVFINDINFO lvfi = { LVFI_STRING, buf1 - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; int iItemFound = ListView_FindItem(hListItem, -1, &lvfi); @@ -933,25 +922,6 @@ return EtatChaine(str2dword(aChStateTable, buf, ec_preferee)); } -#ifdef MODIFY_CHANNELS_BY_CONTROLS -static void ItemToEdit (HWND hDlg, HWND hListItem, int iItem) -{ - wchar_t buf[16]; - - // Mise à jour du numéro dans l'item d'édition: - ListView_GetItemText(hListItem, iItem, CHAN_COL_NO, buf, _countof(buf)); - SetWindowText(GetDlgItem(hDlg, IDC_NUMBER), buf); - - // Mise à jour des cases à cocher liées à l'état : - ListView_GetItemText(hListItem, iItem, CHAN_COL_STATE, buf, _countof(buf)); - - EtatChaine etat = ItemToEtat(hListItem, iItem); - - set_check(hDlg, IDC_ACTIVE, etat!=ec_inactive); - set_check(hDlg, IDC_PREFERRED, etat==ec_preferee); -} -#endif - static INT_PTR CALLBACK RenumberDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { @@ -1014,8 +984,7 @@ switch (uMsg) { case WM_INITDIALOG: - if (ixChaine_ok(ixChaineCourante)) - sidSauveChaineCourante = Canaux[ixChaineCourante].SID; + sidSauveChaineCourante = indexToSID(ixChaineCourante); hListItem = GetDlgItem(hDlg, IDC_CHANNEL_LIST); ListView_SetExStyle(hListItem, LVS_EX_GRIDLINES | LVS_EX_FULLROWSELECT); @@ -1038,33 +1007,6 @@ case WM_COMMAND: switch(wParam) { -#ifdef MODIFY_CHANNELS_BY_CONTROLS - case _cmd_(IDC_NUMBER, EN_KILLFOCUS): - if (iItemEdit>=0){ - // Modification du numéro : - GetWindowText(GetDlgItem(hDlg, IDC_NUMBER), buf, _countof(buf)); - channel_number_changed(hListItem, iItemEdit, buf); - } - break; - - case _cmd_(IDC_ACTIVE, BN_CLICKED): - case _cmd_(IDC_PREFERRED, BN_CLICKED): - if (iItemEdit>=0) { - bool active = get_check(hDlg, IDC_ACTIVE); - bool preferee = get_check(hDlg, IDC_PREFERRED); - EtatChaine etat = - active ? - (preferee ? ec_preferee : ec_active) : - ec_inactive; - - State2LV(hListItem, iItemEdit, etat); - if (!active && preferee) - set_check(hDlg, IDC_PREFERRED, false); - PropSheet_Changed(GetParent(hDlg), hDlg); - } - break; -#endif - case _cmd_(IDC_SCAN, BN_CLICKED): { static bool rechercheEncours = false; @@ -1096,7 +1038,7 @@ LVITEM lvi = { LVIF_PARAM, // mask i // iItem - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; ListView_GetItem(hListItem, &lvi); @@ -1117,13 +1059,8 @@ case _cmd_(IDM_CHAN_INACTIVE, 0): case _cmd_(IDM_CHAN_ACTIVE, 0): case _cmd_(IDM_CHAN_PREFERRED, 0): - if (iItemEdit>=0) { + if (iItemEdit>=0) State2LV(hListItem, iItemEdit, EtatChaine(LOWORD(wParam)-IDM_CHAN_STATE_BASE)); -#ifdef MODIFY_CHANNELS_BY_CONTROLS - EnableModifications(hDlg, TRUE); - ItemToEdit(hDlg, hListItem, iItemEdit); -#endif - } break; } return FALSE; @@ -1131,25 +1068,6 @@ case WM_NOTIFY: switch (_ncode_(lParam)) { -#ifdef MODIFY_CHANNELS_BY_CONTROLS - case NM_CLICK: - // Notification de clic dans un item de la liste. - if (wParam==IDC_CHANNEL_LIST) { - NMITEMACTIVATE & nmia = *(LPNMITEMACTIVATE)lParam; - UINT nbit = ListView_GetSelectedCount(hListItem); - - if (nbit==1) { - iItemEdit = nmia.iItem; - EnableModifications(hDlg, TRUE); - ItemToEdit(hDlg, hListItem, nmia.iItem); - } else { - iItemEdit = -1; - EnableModifications(hDlg, FALSE); - } - } - break; -#endif - case LVN_ENDLABELEDIT: { NMLVDISPINFO & nmlvdi = *(NMLVDISPINFO *)lParam; @@ -1197,7 +1115,7 @@ 0, 0, // state, stateMask, buf, // pszText, _countof(buf) // cchTextMax - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; ListView_GetItem(hListItem, &lvi); @@ -1257,7 +1175,7 @@ delete m_Drag; m_Drag = NULL; } - + if (iItem<0) break; @@ -1274,7 +1192,7 @@ (UINT)~LVIS_SELECTED, // stateMask buf, // pszText _countof(buf), // cchTextMax - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; // Premièrement, copier un item @@ -1303,7 +1221,7 @@ LVHITTESTINFO lvhti = { pt - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; ScreenToClient(hListItem, &lvhti.pt); Modified: trunk/trayicon.cpp =================================================================== --- trunk/trayicon.cpp 2007-12-04 23:15:16 UTC (rev 103) +++ trunk/trayicon.cpp 2007-12-14 14:17:27 UTC (rev 104) @@ -65,7 +65,7 @@ void MyNOTIFYICONDATA::set_icon() { - HICON hNewIcon = recording() ? hRecIcon : hPrgIcon; + HICON hNewIcon = recording()!=0 ? hRecIcon : hPrgIcon; if (hNewIcon != NULL && hNewIcon != hIcon) { hIcon = hNewIcon; From pouchintv-svn at baysse.fr Fri Dec 14 15:40:58 2007 From: pouchintv-svn at baysse.fr (pouchintv-svn at baysse.fr) Date: Fri, 14 Dec 2007 15:40:58 +0100 (CET) Subject: [Pouchintv-dev] [PouchinTVMod] gingko | r105 - trunk Message-ID: <20071214144058.6A1EC5F305@mail.baysse.fr> Author: gingko Date: 2007-12-14 15:40:58 +0100 (Fri, 14 Dec 2007) New Revision: 105 Modified: trunk/epg.cpp Log: Optimisation de l'EPG. Les cha?\195?\174nes marqu?\195?\169es "inactives" ne sont plus affich?\195?\169es dans celui-ci. Modified: trunk/epg.cpp =================================================================== --- trunk/epg.cpp 2007-12-14 14:17:27 UTC (rev 104) +++ trunk/epg.cpp 2007-12-14 14:40:58 UTC (rev 105) @@ -33,85 +33,69 @@ HWND hEpgDlg = NULL; -static void remplit_table_epg(HWND hList) +static void ajout_emission(HWND hListItem, const Chaine & canal, BYTE noEmis, int & iItem) { - LVITEMA item; - memset(&item, 0, sizeof(LVITEMA)); + if (noEmis >=_countof(canal.emis)) + return; - item.mask = LVIF_TEXT | LVIF_PARAM; + const Emission & emis = canal.emis[noEmis]; - int cpt = 0; - int nbChaines = (int)Canaux.size(); - int visible_index = -1; + if (emis.debut.QuadPart != 0) { + LVITEMA item = { + LVIF_TEXT | LVIF_PARAM, // mask + iItem, // iItem + 0, // iSubItem + 0, 0, // state, stateMask + const_cast(canal.nom), // pszText + 0, // cchTextMax + 0, // iImage + MAKELPARAM(canal.SID, noEmis) // lParam + }; - for (int i=0; i(emis.debut_str); + item.iSubItem = 1; + SendMessage(hListItem, LVM_SETITEMTEXTA, iItem, (LPARAM)&item); - SendMessage(hList, LVM_INSERTITEMA, 0, (LPARAM)&item); + item.pszText = const_cast(emis.fin); + item.iSubItem = 2; + SendMessage(hListItem, LVM_SETITEMTEXTA, iItem, (LPARAM)&item); - item.pszText = canal.emis[0].debut_str; - item.iSubItem = 1; - SendMessage(hList, LVM_SETITEMTEXTA, cpt, (LPARAM)&item); + item.pszText = const_cast(emis.nom); + item.iSubItem = 3; + SendMessage(hListItem, LVM_SETITEMTEXTA, iItem, (LPARAM)&item); - item.pszText = canal.emis[0].fin; - item.iSubItem = 2; - SendMessage(hList, LVM_SETITEMTEXTA, cpt, (LPARAM)&item); + item.pszText = const_cast(emis.desc); + item.iSubItem = 4; + SendMessage(hListItem, LVM_SETITEMTEXTA, iItem, (LPARAM)&item); - item.pszText = canal.emis[0].nom; - item.iSubItem = 3; - SendMessage(hList, LVM_SETITEMTEXTA, cpt, (LPARAM)&item); + iItem++; + } +} - item.pszText = canal.emis[0].desc; - item.iSubItem = 4; - SendMessage(hList, LVM_SETITEMTEXTA, cpt, (LPARAM)&item); +static void remplit_table_epg(HWND hListItem) +{ + int iItem = 0; + int visible_index = -1; + int nbChaines = (int)Canaux.size(); - cpt++; - } + for (int i=0; i= 0) { - ListView_SetItemState(hList, visible_index, LVIS_SELECTED , LVIS_SELECTED); - - ListView_EnsureVisible(hList, visible_index, FALSE); + ListView_SetItemState(hListItem, visible_index, LVIS_SELECTED, LVIS_SELECTED); + ListView_EnsureVisible(hListItem, visible_index, FALSE); } - } // Programmation d'un enregistrement : @@ -144,36 +128,41 @@ INT_PTR CALLBACK EpgDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + static HWND hListItem = NULL; + switch (uMsg) { case WM_INITDIALOG: { - HWND hList = GetDlgItem(hDlg, IDC_LIST_EPG); + hListItem = GetDlgItem(hDlg, IDC_LIST_EPG); - ListView_SetExtendedListViewStyle(hList, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP); + ListView_SetExtendedListViewStyle(hListItem, + LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES | LVS_EX_HEADERDRAGDROP); - LVCOLUMN col; + static const struct Tab_cols { + LPCWSTR nom; + int taille; + } t_cols[] = { + {L"Chaîne", 90}, + {L"Début", 90}, + {L"Fin", 90}, + {L"Nom du programme", 220}, + {L"Description", 700} + }; - col.mask = LVCF_TEXT | LVCF_WIDTH; - col.pszText = L"Chaîne"; - col.cx = 90; - ListView_InsertColumn(hList, 0, &col); + for (int i=0; i<_countof(t_cols); i++) { + const Tab_cols & col = t_cols[i]; + LVCOLUMN colonne = { + LVCF_TEXT|LVCF_WIDTH, // mask + 0, // fmt + col.taille, // cx + const_cast(col.nom) // pszText + // le reste à zéro (implicite) + }; - col.cx = 90; - col.pszText = L"Début"; - ListView_InsertColumn(hList, 1, &col); + ListView_InsertColumn(hListItem, i, &colonne); + } - col.pszText = L"Fin"; - ListView_InsertColumn(hList, 2, &col); + remplit_table_epg(hListItem); - col.pszText = L"Nom du programme"; - col.cx = 220; - ListView_InsertColumn(hList, 3, &col); - - col.pszText = L"Description"; - col.cx = 700; - ListView_InsertColumn(hList, 4, &col); - - remplit_table_epg(hList); - SendDlgItemMessage(hDlg, IDC_METHOD_EPG, CB_ADDSTRING, 0, (LPARAM)TEXT("TS")); SendDlgItemMessage(hDlg, IDC_METHOD_EPG, CB_ADDSTRING, 0, (LPARAM)TEXT("PS")); #if USE_RECORD_STREAM @@ -209,57 +198,44 @@ DestroyWindow(hDlg); return TRUE; - case IDC_UPDATE: { - HWND hList = GetDlgItem(hDlg, IDC_LIST_EPG); - ListView_DeleteAllItems(hList); - remplit_table_epg(hList); - return TRUE; } + case IDC_UPDATE: + ListView_DeleteAllItems(hListItem); + remplit_table_epg(hListItem); + return TRUE; case IDC_RECORD: { - LVITEM item; - memset(&item, 0, sizeof(LVITEM)); - item.mask = LVIF_PARAM | LVIF_TEXT; - item.iSubItem = 0; - item.iItem = (int)SendDlgItemMessage(hDlg, IDC_LIST_EPG, LVM_GETSELECTIONMARK, 0, 0); - SendDlgItemMessage(hDlg, IDC_LIST_EPG, LVM_GETITEM, 0, (LPARAM)&item); + LVITEM item = { + LVIF_PARAM, // mask + ListView_GetSelectionMark(hListItem) // iItem + // le reste à zéro (implicite) + }; + + ListView_GetItem(hListItem, &item); + int ixChaine = trouve_chaine_par_sid(LOWORD(item.lParam)); MethodeEnregistrement methode = (MethodeEnregistrement)SendDlgItemMessage(hDlg, IDC_METHOD_EPG, CB_GETCURSEL, 0, 0); AudioMode audio = (AudioMode)SendDlgItemMessage(hDlg, IDC_AUDIO_EPG, CB_GETCURSEL, 0, 0); - epg_add_programme(item.lParam / 2, item.lParam % 2, methode, audio); + epg_add_programme(ixChaine, LOBYTE(HIWORD(item.lParam)), methode, audio); return TRUE; } } return FALSE; case WM_NOTIFY: { LPNMITEMACTIVATE p = (LPNMITEMACTIVATE)lParam; + if (p->hdr.code == NM_DBLCLK && p->hdr.idFrom == IDC_LIST_EPG) { myprintf(L"double clic %i\n", p->iItem); if (p->iItem >= 0) { - char str[256]; - LVITEMA lvi; - memset(&lvi, 0, sizeof(LVITEMA)); + LVITEM item = { + LVIF_PARAM, // mask + p->iItem // iItem + // le reste à zéro (implicite) + }; - lvi.mask = LVIF_TEXT; - lvi.cchTextMax = 256; - lvi.pszText = str; - lvi.iItem = p->iItem; - - SendMessageA(p->hdr.hwndFrom, LVM_GETITEMTEXTA, p->iItem, (LPARAM)&lvi); - - - myprintf(L"nom %S\n", str); - - // cherche chaîne avec ce nom - int nbChaines = (int)Canaux.size(); - - for(int i =0; i Author: gingko Date: 2007-12-15 02:11:13 +0100 (Sat, 15 Dec 2007) New Revision: 107 Modified: trunk/recprog.cpp trunk/recprog.h Log: Correction d'un oubli dans la r106 : Les t?\195?\162ches programm?\195?\169es n'?\195?\169taient plus supprim?\195?\169es du planificateur de t?\195?\162ches en m?\195?\170me temps que la programmation correspondante, car le pr?\195?\169fixe "PTV-" que j'avais inclus pour nommer l'ajout ne l'avait pas ?\195?\169t?\195?\169 pour nommer la suppression. Par la m?\195?\170me occasion, j'ai remplac?\195?\169 le "/" par un "-" dans les noms de programmations auto-g?\195?\169n?\195?\169r?\195?\169s car le r?\195?\169sultat n'?\195?\169tait pas des plus heureux une fois transf?\195?\169r?\195?\169 dans le planificateur de t?\195?\162ches. Modified: trunk/recprog.cpp =================================================================== --- trunk/recprog.cpp 2007-12-14 16:03:59 UTC (rev 106) +++ trunk/recprog.cpp 2007-12-15 01:11:13 UTC (rev 107) @@ -155,29 +155,6 @@ return identifie; } -// Supprimer une tâche depuis le programmateur de tâches : -static int supprime_une_tache(LPCSTR nom) -{ - WCHAR pwszTaskName[256]; - ITaskScheduler * pITS; - - NomProtege nom_tache(nom, '_'); - - nom_tache.ToWide(pwszTaskName, _countof(pwszTaskName)); - - HRESULT hr = CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, (void**)&pITS); - if (FAILED(hr)) { - myprintf(L"Erreur init task scheduler\n"); - return 1; - } - - // Supprime la tâche - pITS->Delete(pwszTaskName); - - pITS->Release(); - return 0; -} - // Constructeur : Programme::Programme() { @@ -187,7 +164,6 @@ } - // Retourne 'true' si intersection temporelle avec la programmation 'p2' bool Programme::overlap(const Programme & p2) const { @@ -465,7 +441,7 @@ { // Par précaution, détruire d'abord toute tâche antérieurement existante portant // le nom demandé : - supprime_une_tache(nom); + supprimerTacheProgrammee(); if (etat==epr_actif && tache == planif_ajout) { wchar_t username[256]; @@ -489,6 +465,28 @@ return false; } +// Supprimer cette programmation des tâches programmées (retourne 'false' si échec) +bool Programme::supprimerTacheProgrammee() const +{ + WCHAR taskName[_countof(nom)+4]; + ITaskScheduler * pITS; + + // On génère le nom de la tâche : + genereNomTache(taskName, _countof(taskName)); + + HRESULT hr = CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, (void**)&pITS); + if (FAILED(hr)) { + myprintf(L"Erreur init task scheduler\n"); + return false; + } + + // Supprime la tâche + pITS->Delete(taskName); + + pITS->Release(); + return true; +} + /** * Traitement du chargement de la liste des programmes au démarrage, et purge * des enregistrements périmés : @@ -879,7 +877,7 @@ // do { ixSuffNom++; - sprintf_s(nom, _countof(nom), ixSuffNom>1 ? "%s-%i/%02i_%u" : "%s-%i/%02i", + sprintf_s(nom, _countof(nom), ixSuffNom>1 ? "%s-%i-%02i_%u" : "%s-%i-%02i", prog.isMultiplex() ? "Multiplex" : canal.nom, prog.debut.wDay, prog.debut.wMonth, ixSuffNom); } while (strcmp(nom, prog.nom)!=0 && trouve_prog_par_nom(nom)>=0); @@ -1174,7 +1172,7 @@ ListView_DeleteItem(hListItem, selection); // Suppression éventuelle tâche antérieure portant ce nom : - supprime_une_tache(Programmes[selection].nom); + Programmes[selection].supprimerTacheProgrammee(); Programmes.erase(Programmes.begin() + selection); finalize_prog_change(); @@ -1196,7 +1194,7 @@ L"Erreur lors de la modification", MB_ICONERROR); else { // Suppression éventuelle tâche antérieure portant l'ancien nom : - supprime_une_tache(prog.nom); + prog.supprimerTacheProgrammee(); prog = newprog; @@ -1204,7 +1202,6 @@ prog.ajoutTacheAvecLogon(hDlg); finalize_prog_change(); - //set_timer_record_end(); } } } Modified: trunk/recprog.h =================================================================== --- trunk/recprog.h 2007-12-14 16:03:59 UTC (rev 106) +++ trunk/recprog.h 2007-12-15 01:11:13 UTC (rev 107) @@ -118,7 +118,10 @@ // Ajout de cette programmation aux tâches programmées (retourne 'false' si échec) bool ajouterTacheProgrammee(LPCWSTR username, LPCWSTR motdepasse) const; - // + + // Supprimer cette programmation des tâches programmées (retourne 'false' si échec) + bool supprimerTacheProgrammee() const; + // Ajout de la programmation si nécessaire, incluant identification bool ajoutTacheAvecLogon(HWND hDlg); From gingko_pouchintv at nospam.homelinux.org Sat Dec 15 02:32:48 2007 From: gingko_pouchintv at nospam.homelinux.org (Gingko) Date: Sat, 15 Dec 2007 02:32:48 +0100 Subject: [Pouchintv-dev] r106 ? References: <20071215011113.619765F30F@mail.baysse.fr> Message-ID: <00ab01c83eba$66dd7dd0$0a00a8c0@gilles> Bonjour, Je pense qu'il serait utile d'augmenter encore la taille limite des messages générés par le script de mise à jour : Celui qui aurait dû avoir été envoyé suite à la mise à jour r106 est manifestement resté bloqué. Gingko From pouchintv-svn at baysse.fr Sat Dec 15 21:29:36 2007 From: pouchintv-svn at baysse.fr (pouchintv-svn at baysse.fr) Date: Sat, 15 Dec 2007 21:29:36 +0100 (CET) Subject: [Pouchintv-dev] [PouchinTVMod] gingko | r108 - trunk Message-ID: <20071215202936.466895F305@mail.baysse.fr> Author: gingko Date: 2007-12-15 21:29:35 +0100 (Sat, 15 Dec 2007) New Revision: 108 Modified: trunk/main.cpp trunk/record.cpp trunk/recprog.cpp trunk/res.rc trunk/resource.h Log: Modification du syst?\195?\168me de validation du dialogue d'arr?\195?\170t temporis?\195?\169 d'enregistrement, afin d'?\195?\169viter certaines apparitions d'un message de confirmation l?\195?\160 o?\195?\185 l'utilisateur n'a rien modifi?\195?\169, et pour le rendre plus intuitif. Correction d'un bug qui provoquait parfois l'arr?\195?\170t intempestif de plusieurs enregistrements lors de modifications dans ce m?\195?\170me dialogue d'arr?\195?\170t temporis?\195?\169 d'enregistrement. Correction d'un bug qui produisait un plantage lors du changement de cha?\195?\174ne par d?\195?\169cr?\195?\169mentation (fl?\195?\168ches ou molette souris) si la cha?\195?\174ne en cours ?\195?\169tait la premi?\195?\168re de la liste. Le dialogue qui confirme la fermeture s'il y a des enregistrements en cours annonce maintenant ces enregistrements au pluriel, le cas ?\195?\169ch?\195?\169ant. Modified: trunk/main.cpp =================================================================== --- trunk/main.cpp 2007-12-15 01:11:13 UTC (rev 107) +++ trunk/main.cpp 2007-12-15 20:29:35 UTC (rev 108) @@ -1130,11 +1130,24 @@ **/ static INT_PTR CALLBACK ConfirmCloseProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { - case WM_INITDIALOG: + case WM_INITDIALOG: { // Initialisation de la fenêtre + int nombre = 0; + // Compter les enregistrements en cours : + for (int i=0; i<_countof(enregistrements_actuels); i++) + if (enregistrements_actuels[i].recording()) + nombre++; + + if (nombre > 1) { + wchar_t buf[64]; + + swprintf_s(buf, _countof(buf), L"%i enregistrements sont en cours", nombre); + SetWindowText(GetDlgItem(hwndDlg, IDC_MESSAGE), buf); + } + // Renvoie True car la fenêtre a été initialisée - return TRUE; + return TRUE; } case WM_COMMAND: // Commande reçue (clic, etc...) @@ -1478,7 +1491,11 @@ bool enregistre = recording()!=0; long freq_courante = Canaux[ixChaineCourante].frequence; - for (int i = ixChaineCourante+pas; i!=ixChaineCourante; i = (i+nbChaines+pas)%nbChaines) { + for (int i = ixChaineCourante ;; ) { + i = (i+nbChaines+pas)%nbChaines; + if (i==ixChaineCourante) + break; + const Chaine & canal = Canaux[i]; if (canal.etat != ec_preferee) Modified: trunk/record.cpp =================================================================== --- trunk/record.cpp 2007-12-15 01:11:13 UTC (rev 107) +++ trunk/record.cpp 2007-12-15 20:29:35 UTC (rev 108) @@ -136,7 +136,8 @@ **/ long Enregistrement::temps_restant(const SYSTEMTIME & localtime) const { - if (!recording()) + if (!recording() || apres==apr_null) + // Si pas en train d'enregistrer, ou bien si pas d'arrêt programmé return DUREE_INFINIE; long reste = DiffTime(fin, localtime); Modified: trunk/recprog.cpp =================================================================== --- trunk/recprog.cpp 2007-12-15 01:11:13 UTC (rev 107) +++ trunk/recprog.cpp 2007-12-15 20:29:35 UTC (rev 108) @@ -1355,6 +1355,13 @@ return time; } +enum DlgEditState { + des_notModified, // Aucun changement à appliquer + des_noConfirmNeed, // Un changement peut-être appliqué mais son annulation n'a pas + // besoin d'être explicite + des_needConfirm // Un changement a été effectué mais son annulation doit être explicite +}; + struct DelayEditData { HWND hDlg; HWND hEnrCombo; @@ -1368,15 +1375,15 @@ bool useEndTime; // vrai si le temps mémorisé (refTime) est l'heure de fin bool dispEndTime; // vrai si le contrôle affiche l'heure de fin bool delayActive; // vrai si la temporisation sera activée - bool dlgModified; // vrai si on a changé quelque chose + DlgEditState dlgEditState; // niveau de modifications effectuées void remplitCombo(); void load(bool initActive); // charger les données de l'enregistrement N° 'grab' - void setCtlTime(); // Transférer le temps de référence dans le contrôle, selon le mode courant + void setCtlTime(); // Transférer le temps de référence dans le contrôle, selon le mode courant bool needTimer() const {return delayActive && dispEndTime != useEndTime;} - void toEndTime(void); // convertir en mode "heure de fin" - void apply(void); // appliquer les changements - void setChangedState(bool changed); // Indique si le contenu du dialogue a été modifié ou pas + void toEndTime(void); // convertir en mode "heure de fin" + void apply(void); // appliquer les changements + void setChangedState(DlgEditState newState); // Indique comment le contenu du dialogue a été modifié ou pas void setActiveState(bool active); // Modifie l'état "actif" de la fin d'enregistrement programmée void recordEnded(void); }; @@ -1428,7 +1435,7 @@ SetCtlAfter(hDlg, IDC_AFTER_POWEROFF, IDC_AFTER, penreg->apres); refTime = penreg->fin; useEndTime = true; - setChangedState(false); + setChangedState(des_notModified); } else { setActiveState(initActive); GetLocalTime(&refTime); @@ -1436,7 +1443,7 @@ refTime.wHour = refTime.wSecond = refTime.wMilliseconds = 0; refTime.wMinute = 30; useEndTime = false; - setChangedState(initActive); + setChangedState(initActive ? des_noConfirmNeed : des_notModified); } } CheckDlgButton(hDlg, IDC_DELAY_ACTIVE, delayActive); @@ -1464,10 +1471,16 @@ } // Indique si le contenu du dialogue a été modifié ou pas -void DelayEditData::setChangedState(bool changed) +void DelayEditData::setChangedState(DlgEditState newState) { - dlgModified = changed; - EnableWindow(GetDlgItem(hDlg, IDC_APPLY), dlgModified); + bool modified = newState!=des_notModified; + bool oldActive = penreg && penreg->apres!=apr_null; + bool activeChanged = delayActive != oldActive; + + dlgEditState = newState; + EnableWindow(GetDlgItem(hDlg, IDC_APPLY), modified && (delayActive || activeChanged)); + SetWindowText(GetDlgItem(hDlg, IDC_DELAY_ACTIVE), + delayActive != oldActive ? L"Temporisation active (après validation)" : L"Temporisation active"); } void DelayEditData::toEndTime(void) @@ -1500,7 +1513,7 @@ // Application des modifications : void DelayEditData::apply(void) { - if (penreg->recording()) { // Toujours en train d'enregistrer ? + if (penreg->recording() && dlgEditState!=des_notModified) { // Toujours en train d'enregistrer ? ApresEnregistrement apres = delayActive ? GetCtlAfter(hDlg, IDC_AFTER_POWEROFF, IDC_AFTER) : apr_null; @@ -1539,7 +1552,7 @@ programmation_modifiee = true; } - setChangedState(false); + setChangedState(des_notModified); PostMessage(hMainWnd, WM_TIMECHANGE, 0, 0); } } @@ -1579,7 +1592,7 @@ int newSel = SendMessage(edat.hEnrCombo, CB_GETCURSEL, 0, 0); if (newSel!=CB_ERR && newSel!=edat.comboSel) { - if (edat.dlgModified) { + if (edat.dlgEditState==des_needConfirm) { switch (MessageBox(hDlg, L"Vous avez apporté des modifications à la programmation de la chaîne en cours.\n" L"Désirez-vous valider ces modifications ?", @@ -1597,7 +1610,7 @@ } edat.comboSel = newSel; edat.grab = SendMessage(edat.hEnrCombo, CB_GETITEMDATA, WPARAM(newSel), 0); - edat.load(false); + edat.load(true); } return TRUE; } @@ -1609,12 +1622,12 @@ case _cmd_(IDC_DELAY_ACTIVE, BN_CLICKED): edat.setActiveState(IsDlgButtonChecked(hDlg, IDC_DELAY_ACTIVE)==BST_CHECKED); - edat.setChangedState(true); + edat.setChangedState(des_needConfirm); return TRUE; case _cmd_(IDC_AFTER, BN_CLICKED): case _cmd_(IDC_AFTER_POWEROFF, BN_CLICKED): - edat.setChangedState(true); + edat.setChangedState(des_needConfirm); return TRUE; case _cmd_(IDC_APPLY, BN_CLICKED): @@ -1636,7 +1649,7 @@ DateTime_GetSystemtime(edat.hTimeCtl, &edat.refTime); // Le mode d'affichage utilisé sert maintenant de référence : edat.useEndTime = edat.dispEndTime; - edat.setChangedState(true); + edat.setChangedState(des_needConfirm); } return TRUE; } Modified: trunk/res.rc =================================================================== --- trunk/res.rc 2007-12-15 01:11:13 UTC (rev 107) +++ trunk/res.rc 2007-12-15 20:29:35 UTC (rev 108) @@ -318,7 +318,7 @@ BEGIN RTEXT "Chaîne enregistrée :",IDC_STATIC,7,9,70,8 COMBOBOX IDC_RECORD,83,7,76,37,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - CONTROL "Temporisation active (après validation)",IDC_DELAY_ACTIVE, + CONTROL "Temporisation active",IDC_DELAY_ACTIVE, "Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,24,138,10 CONTROL "",IDC_DELAYED_STOP,"SysDateTimePick32",DTS_UPDOWN | WS_TABSTOP | 0x8,7,38,48,12 LTEXT "hh:mm:ss",IDC_DELAY_LABEL,9,51,36,10 @@ -339,7 +339,7 @@ CAPTION "Confirmation" FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - CTEXT "Un enregistrement est en cours",IDC_STATIC,21,25,107,10 + CTEXT "Un enregistrement est en cours",IDC_MESSAGE,21,25,107,10 DEFPUSHBUTTON "Fermer",IDOK,18,54,54,14 PUSHBUTTON "Annuler",IDCANCEL,78,54,54,14 END Modified: trunk/resource.h =================================================================== --- trunk/resource.h 2007-12-15 01:11:13 UTC (rev 107) +++ trunk/resource.h 2007-12-15 20:29:35 UTC (rev 108) @@ -42,6 +42,7 @@ #define IDC_SIGNAL_LOCK 1016 #define IDC_SIGNAL_QUALITY 1017 #define IDC_SIGNAL_STRENGTH 1018 +#define IDC_MESSAGE 1020 #define IDC_VERSION 1021 #define IDC_ABOUT_ICON 1022 #define IDC_NEW 1023 From pouchintv-svn at baysse.fr Thu Dec 20 14:35:07 2007 From: pouchintv-svn at baysse.fr (pouchintv-svn at baysse.fr) Date: Thu, 20 Dec 2007 14:35:07 +0100 (CET) Subject: [Pouchintv-dev] [PouchinTVMod] gingko | r109 - trunk Message-ID: <20071220133507.150925F314@mail.baysse.fr> Author: gingko Date: 2007-12-20 14:35:06 +0100 (Thu, 20 Dec 2007) New Revision: 109 Modified: trunk/graph.cpp trunk/main.cpp trunk/record.cpp trunk/recprog.cpp trunk/trayicon.cpp Log: Correction d'un bug introduit par moi-m?\195?\170me ?\195?\160 la r103, qui faisait que les mises ?\195?\160 jour du menu de l'ic?\195?\180ne de notification ne s'effectuaient plus correctement. Les pages du dialogue d'arr?\195?\170t temporis?\195?\169 sont maintenant initialis?\195?\169es d?\195?\169sactiv?\195?\169es sur les enregistrements o?\195?\185 cet arr?\195?\170t n'est pas encore programm?\195?\169. Optimisations et factorisations, notamment concernant la gestion de l'OSD. Quelques tests d'erreurs ajout?\195?\169s. Modified: trunk/graph.cpp =================================================================== --- trunk/graph.cpp 2007-12-15 20:29:35 UTC (rev 108) +++ trunk/graph.cpp 2007-12-20 13:35:06 UTC (rev 109) @@ -650,7 +650,8 @@ } } - pSoundPin->QueryInterface(&pMapSound); + hr = pSoundPin->QueryInterface(&pMapSound); + myprintf(L"%?erreur pMapSound, hr=0x%08x\n", FAILED(hr), hr); pSoundPin->Release(); return 0; @@ -733,7 +734,8 @@ } } - pAc3Pin->QueryInterface(&pMapAc3); + hr = pAc3Pin->QueryInterface(&pMapAc3); + myprintf(L"%?erreur pMapAc3, hr=0x%08x\n", FAILED(hr), hr); pAc3Pin->Release(); return 0; @@ -772,7 +774,7 @@ // query MPEG-2 Sections and Tables Filter - pMpeg2Filter->QueryInterface(&pMpeg2Data); + hr = pMpeg2Filter->QueryInterface(&pMpeg2Data); if (FAILED(hr)) { erreur(L"pas eu le mpeg2data"); @@ -830,8 +832,7 @@ } IPin * pPinEntrée = cherche_pin(pEPGFilter, PINDIR_INPUT); - if (pEPGFilter == NULL) - { + if (pEPGFilter == NULL) { erreur(L"pas trouvé pin entrée"); return 1; } @@ -843,17 +844,16 @@ erreur(L"pas connecté pin entrée"); return 1; } - } IMPEG2PIDMap * pMapEpg; - pEpgPin->QueryInterface(&pMapEpg); + hr = pEpgPin->QueryInterface(&pMapEpg); + if (SUCCEEDED(hr)) { + ULONG pid = 18; - ULONG pid = 18; - - pMapEpg->MapPID(1, &pid, MEDIA_MPEG2_PSI); - - pMapEpg->Release(); + pMapEpg->MapPID(1, &pid, MEDIA_MPEG2_PSI); + pMapEpg->Release(); + } pEpgPin->Release(); return 0; @@ -909,7 +909,8 @@ } - pPmtPin->QueryInterface(&pMapPmt); + hr = pPmtPin->QueryInterface(&pMapPmt); + myprintf(L"%?erreur pMapPmt, hr=0x%08x\n", FAILED(hr), hr); pPmtPin->Release(); Modified: trunk/main.cpp =================================================================== --- trunk/main.cpp 2007-12-15 20:29:35 UTC (rev 108) +++ trunk/main.cpp 2007-12-20 13:35:06 UTC (rev 109) @@ -326,7 +326,23 @@ return etat_fenetre(GetWindowLongPtr(hWnd, GWL_STYLE)); } -static void update_coords(void) { +// Obtenir le rectangle de l'espace affichable pour la video : +static void GetVideoRect(RECT & rcDest) +{ + // Obtenir le rectangle client de la fenêtre : + GetClientRect(hMainWnd, &rcDest); + + // Si la barre d'état est présente, retirer la hauteur correspondante : + if (hStatus != NULL) { + RECT srect; + + GetWindowRect(hStatus, &srect); + rcDest.bottom -= (srect.bottom - srect.top); + } +} + +static void update_coords(void) +{ static DWORD last_tick = 0; DWORD tick = GetTickCount(); @@ -340,22 +356,12 @@ last_tick = tick; RECT rcDest; - // Get the window client area. - GetClientRect(hMainWnd, &rcDest); - if (hStatus != NULL) { - RECT rect; - GetWindowRect(hStatus, &rect); + // Récupère le rectangle de la video : + GetVideoRect(rcDest); - LONG height = rect.bottom - rect.top; - - rcDest.bottom -= height; - } - // on met le facteur de zoom - - if (use_all_width) - { + if (use_all_width) { // hauteur ideale LONG h = (rcDest.right * 3) / 4; @@ -461,7 +467,7 @@ // Arrêt de l'enregistrement : cet item est actif si l'enregistement de la chaîne // courante (à l'exclusion de tout autre) est actif. - set_menu_enable_state(hMenu, IDM_STOP_RECORD, curChannelRecording); + set_menu_enable_state(hRecMenu, IDM_STOP_RECORD, curChannelRecording); // Pour chaque **autre** enregistrement existant, multiplex inclus, un item d'arrêt // spécifique est ajouté et activé. Cet item est détruit si l'enregistrement @@ -489,70 +495,9 @@ // Arrêt d'enregistrement retardé : cet item est actif si au moins un enregistrement // est en cours, quel qu'il soit : - set_menu_enable_state(hMenu, IDM_DELAYED_STOP, active); + set_menu_enable_state(hRecMenu, IDM_DELAYED_STOP, active); } -/** - * Recherche le numéro du menu en enlevant les "&" le cas échéant - **/ -/* -// [pas utile aussi longtemps que les positions des sous-menus sont constantes] -static int cherche_menu(const HMENU hMenu, LPCWSTR nom_menu) -{ - LPWSTR cherche_strip = new wchar_t[wcslen(nom_menu)+1]; - int ret = -1; - - if (wcspbrk(nom_menu, L"&") != 0) { - UINT j=0; - for (UINT i=0; iQueryInterface(&pPages); if (FAILED(hr)) { - erreur(L"Ce filtre n'a pas de property page"); + erreur(L"Ce filtre n'a pas de page de propriétés"); return; } IUnknown * pUnk; - pPages->QueryInterface(&pUnk); + hr = pPages->QueryInterface(&pUnk); - LCID lcid = GetUserDefaultLCID(); + if (SUCCEEDED(hr)) { + CAUUID caGUID; - CAUUID caGUID; - pPages->GetPages(&caGUID); + hr = pPages->GetPages(&caGUID); + if (SUCCEEDED(hr)) { + LCID lcid = GetUserDefaultLCID(); + ShowCursor(TRUE); hr = OleCreatePropertyFrame( @@ -1285,15 +1230,17 @@ NULL ); ShowCursor(FALSE); + CoTaskMemFree(caGUID.pElems); if (FAILED(hr)) { - erreur(L"Erreur lors de la création de la property page"); + erreur(L"Erreur lors de la création de la page de propriétés"); + } } pUnk->Release(); + } pPages->Release(); - CoTaskMemFree( caGUID.pElems ); } static void traite_numero(int nb) @@ -1697,90 +1644,105 @@ } } -static void do_osd(HDC hdc) +// Obtenir le ration d'aspect : +static POINT GetAspectRatio() { - if (ixChaineCourante >= 0) { - DWORD current = GetTickCount(); + POINT ar = {4, 3}; // (valeurs par défaut) - bool show_prog = current - last_zapping < DUREE_OSD; - bool show_volume = current - last_volume_osd < DUREE_OSD; - bool show_zoom = current - last_zoom < DUREE_OSD; - bool show_chaine = current - last_chaine < DUREE_OSD; + if (pVMR != NULL) { // pVMR pas toujours défini à cet endroit + IPin * pPin = cherche_pin(pVMR, PINDIR_INPUT); - // N'affiche l'OSD que si on fait des mises à jour - if (show_prog || show_volume || show_zoom || muted || show_chaine) { - DWORD ar_x = 4; - DWORD ar_y = 3; + if (pPin != NULL) { + AM_MEDIA_TYPE am; + HRESULT hr = pPin->ConnectionMediaType(&am); - if (pVMR != NULL) { // pVMR pas toujours défini à cet endroit - IPin * pPin = cherche_pin(pVMR, PINDIR_INPUT); + pPin->Release(); - if (pPin != NULL) { - AM_MEDIA_TYPE am; - HRESULT hr = pPin->ConnectionMediaType(&am); + if (SUCCEEDED(hr)) { + VIDEOINFOHEADER2 * vih = (VIDEOINFOHEADER2 *)am.pbFormat; - pPin->Release(); + ar.x = vih->dwPictAspectRatioX; + ar.y = vih->dwPictAspectRatioY; - if (SUCCEEDED(hr)) { + //myprintf(L"Aspect ratio: x=%i, y=%i\n", ar.x, ar.y); - VIDEOINFOHEADER2 * vih = (VIDEOINFOHEADER2 *)am.pbFormat; - - //myprintf(L"x %i, y %i\n", vih->dwPictAspectRatioX, vih->dwPictAspectRatioY); - - ar_x = vih->dwPictAspectRatioX; - ar_y = vih->dwPictAspectRatioY; - - FreeMediaType(am); - } - } + FreeMediaType(am); } + } + } + return ar; +} - RECT rect; - // Récupère la taille de la fenêtre - GetClientRect(hMainWnd, &rect); +// Déterminer le rectangle utilisable pour l'OSD : +static void GetOsdRect(RECT & rect) +{ + POINT ar = GetAspectRatio(); // Ratio d'aspect + LONG ideal; + LONG retranche; - // Si la barre de status est affichée, retire sa hauteur à la taille de la fenêtre - if (hStatus != NULL) { - RECT srect; - GetWindowRect(hStatus, &srect); + // Récupère le rectangle de la video : + GetVideoRect(rect); - LONG height = srect.bottom - srect.top; + // on corrige la zone que l'on va utiliser pour dessiner l'osd + int diff = rect.bottom * ar.x - rect.right * ar.y; - rect.bottom -= height; - } + if (diff > 0) { + // trop haut de cb de pixels ? + ideal = (rect.right * ar.y) / ar.x; + retranche = (rect.bottom - ideal) / 2; + rect.top += retranche; + rect.bottom -= retranche; + } else if (diff < 0 && !use_all_width && !etirer_video) { + // trop large de cb de pixels ? + ideal = (rect.bottom * ar.x) / ar.y; + retranche = (rect.right - ideal) / 2; + rect.left += retranche; + rect.right -= retranche; + } +} - // on corrige la zone que l'on va utiliser pour dessiner l'osd +static void text_osd(HDC hDC, int x, int y, UINT align, LPCSTR fmt, ...) +{ + char str[256]; + int taille; + va_list argptr; - int diff = rect.bottom * ar_x - rect.right * ar_y; + va_start(argptr, fmt); + taille = vsprintf_s(str, _countof(str), fmt, argptr); + va_end(argptr); + SetTextAlign(hDC, align); + TextOutA(hDC, x, y, str, taille); +} - if (diff > 0) { - // trop haut de cb de pixels ? +static void do_osd(HDC hdc) +{ + if (ixChaineCourante >= 0) { + DWORD current = GetTickCount(); - LONG hauteur_ideale = (rect.right * ar_y) / ar_x; + bool show_prog = current - last_zapping < DUREE_OSD; + bool show_volume = current - last_volume_osd < DUREE_OSD; + bool show_zoom = current - last_zoom < DUREE_OSD; + bool show_chaine = current - last_chaine < DUREE_OSD; - LONG retranche = (rect.bottom - hauteur_ideale) / 2; + // N'affiche l'OSD que si on fait des mises à jour + if (show_prog || show_volume || show_zoom || muted || show_chaine) { + RECT rect; - rect.top += retranche; - rect.bottom -= retranche; - } - else if (diff < 0 && !use_all_width && !etirer_video) { - // trop large de cb de pixels ? + // Récupère le rectangle de l'OSD : + GetOsdRect(rect); - LONG largeur_ideale = (rect.bottom * ar_x) / ar_y; + LONG hauteur = min(rect.bottom, rect.right)/12; - LONG retranche = (rect.right - largeur_ideale) / 2; + // Retirer systématiquement 10 pixels à gauche et à droite : + rect.left += 10; + rect.right -= 10; - rect.left += retranche; - rect.right -= retranche; - } - // On génère la police pour l'affichage de l'OSD - LOGFONT lf; - memset(&lf, 0, sizeof(LOGFONT)); + LOGFONT lf = { + hauteur // lfHeight + // le reste à zéro (implicite) + }; - LONG hauteur = min(rect.bottom, rect.right)/12; - - lf.lfHeight = hauteur; HFONT hf = CreateFontIndirect(&lf); SelectObject(hdc, hf); @@ -1791,77 +1753,52 @@ SetBkMode(hdc, TRANSPARENT); // Comme le numéro de la chaine et la chaine sont au même endroit, - // n'affiche pas la chaine si on tappe le numéro + // n'affiche que le plus récent des deux : if (show_prog && show_chaine) { if(last_chaine > last_zapping) - show_prog = !show_prog; + show_prog = false; else - show_chaine = !show_chaine; + show_chaine = false; } // Affiche le nom de la chaine + programme en haut/droite - if (show_prog) { - SetTextAlign(hdc, TA_RIGHT); + if (show_prog && ixChaine_ok(ixChaineCourante)) { + Chaine & canal = Canaux[ixChaineCourante]; - Chaine & ch = Canaux[ixChaineCourante]; - - TextOutA(hdc, rect.right-10, rect.top, ch.nom, (int)strlen(ch.nom)); - - if (ch.emis[0].debut.QuadPart) { - TextOutA(hdc, rect.right-10, rect.top+hauteur, ch.emis[0].nom, (int)strlen(ch.emis[0].nom)); - } + text_osd(hdc, rect.right, rect.top, TA_RIGHT, "%s", canal.nom); + if (canal.emis[0].debut.QuadPart) + text_osd(hdc, rect.right, rect.top+hauteur, TA_RIGHT, + "%s", canal.emis[0].nom); } // Affiche les numéros saisis pour le choix des chaines en haut/droite - if (show_chaine) { - if (numeroChaineSaisi > 999) { - numeroChaineSaisi = 0; - KillTimer(hMainWnd, TIMER_NUMBER); - } else if (numeroChaineSaisi > 0) { - SetTextAlign(hdc, TA_RIGHT); + if (show_chaine && numeroChaineSaisi > 0) + text_osd(hdc, rect.right, rect.top, TA_RIGHT, + "%i", numeroChaineSaisi); - char numeros[5]; - _itoa_s(numeroChaineSaisi, numeros, _countof(numeros), 10); - //myprintf(L"Actualise la chaine: %d\t%S\n", nouvelleChaine, numeros); - - TextOutA(hdc, rect.right-10, rect.top, numeros, (int)strlen(numeros)); - } - } - // Affiche si on est en "Sourdine" en bas/droite - if (muted) { - SetTextAlign(hdc, TA_RIGHT); + if (muted) + text_osd(hdc, rect.right, rect.bottom-hauteur, TA_RIGHT, "Sourdine"); - TextOutA(hdc, rect.right-10, rect.bottom-hauteur, "Sourdine", (int)strlen("Sourdine")); - } - - // on affiche le dernier message appelé et on cache l'ancien + // Comme le volume et le zoom sont au même endroit, + // n'affiche que le plus récent des deux : if (show_volume && show_zoom) { - if(last_volume_osd > last_zoom) show_zoom = !show_zoom; - else show_volume = !show_volume; + if (last_volume_osd > last_zoom) + show_zoom = false; + else + show_volume = false; } // Affiche le volume en bas/gauche - if (show_volume) { - SetTextAlign(hdc, TA_LEFT); + if (show_volume) + text_osd(hdc, rect.left, rect.bottom-hauteur, TA_LEFT, + "Volume %i", (volumeCourant+8000)/80); - char str[256]; - int taille = sprintf_s(str, _countof(str), "Volume %i", (volumeCourant+8000)/80); - - TextOutA(hdc, rect.left+10, rect.bottom-hauteur, str, taille); - } - // Affiche le zoom en bas/gauche - if (show_zoom) { - SetTextAlign(hdc, TA_LEFT); + if (show_zoom) + text_osd(hdc, rect.left, rect.bottom-hauteur, TA_LEFT, + "Zoom %i%%", zoom_ratio); - char str[256]; - int taille = sprintf_s(str, _countof(str), "Zoom %i%%", zoom_ratio); - - TextOutA(hdc, rect.left+10, rect.bottom-hauteur, str, taille); - } - - DeleteObject(hf); } } Modified: trunk/record.cpp =================================================================== --- trunk/record.cpp 2007-12-15 20:29:35 UTC (rev 108) +++ trunk/record.cpp 2007-12-20 13:35:06 UTC (rev 109) @@ -106,8 +106,12 @@ void Enregistrement::query(IBaseFilter * pGrabber) { - if (pSample==NULL) - pGrabber->QueryInterface(&pSample); + + + if (pSample==NULL) { + HRESULT hr = pGrabber->QueryInterface(&pSample); + myprintf(L"%?erreur query pSample, hr=0x%08x\n", FAILED(hr), hr); + } } void Enregistrement::free() Modified: trunk/recprog.cpp =================================================================== --- trunk/recprog.cpp 2007-12-15 20:29:35 UTC (rev 108) +++ trunk/recprog.cpp 2007-12-20 13:35:06 UTC (rev 109) @@ -1479,8 +1479,6 @@ dlgEditState = newState; EnableWindow(GetDlgItem(hDlg, IDC_APPLY), modified && (delayActive || activeChanged)); - SetWindowText(GetDlgItem(hDlg, IDC_DELAY_ACTIVE), - delayActive != oldActive ? L"Temporisation active (après validation)" : L"Temporisation active"); } void DelayEditData::toEndTime(void) @@ -1569,7 +1567,7 @@ edat.hEnrCombo = GetDlgItem(hDlg, IDC_RECORD); edat.hTimeCtl = GetDlgItem(hDlg, IDC_DELAYED_STOP); edat.grab = getGrabberChaine(ixChaineCourante); - edat.load(true); + edat.load(false /*true*/); edat.remplitCombo(); // Chargement du combo des enregistrements // Création d'un timer battant à la seconde pour la mise à jour du contrôle de saisie @@ -1610,7 +1608,7 @@ } edat.comboSel = newSel; edat.grab = SendMessage(edat.hEnrCombo, CB_GETITEMDATA, WPARAM(newSel), 0); - edat.load(true); + edat.load(false /*true*/); } return TRUE; } Modified: trunk/trayicon.cpp =================================================================== --- trunk/trayicon.cpp 2007-12-15 20:29:35 UTC (rev 108) +++ trunk/trayicon.cpp 2007-12-20 13:35:06 UTC (rev 109) @@ -46,7 +46,7 @@ if (hMainWnd != NULL) { hWnd = hMainWnd; uID = MY_TRAY_ICON_ID; - myprintf(L"MyNOTIFYICONDATA::notify() - msg=%u, flg=0x%02x\n", dwMessage, uFlags); + //myprintf(L"MyNOTIFYICONDATA::notify() - msg=%u, flg=0x%02x\n", dwMessage, uFlags); return Shell_NotifyIcon(dwMessage, this)==TRUE; } return false; From pouchintv-svn at baysse.fr Thu Dec 20 17:14:18 2007 From: pouchintv-svn at baysse.fr (pouchintv-svn at baysse.fr) Date: Thu, 20 Dec 2007 17:14:18 +0100 (CET) Subject: [Pouchintv-dev] [PouchinTVMod] gingko | r110 - trunk Message-ID: <20071220161418.2AEFD5F301@mail.baysse.fr> Author: gingko Date: 2007-12-20 17:14:17 +0100 (Thu, 20 Dec 2007) New Revision: 110 Modified: trunk/channels.cpp trunk/channels.h trunk/grabber.cpp trunk/grabber.h trunk/ini.cpp trunk/main.cpp trunk/parse.cpp trunk/pmtfilter.cpp trunk/record.cpp trunk/recprog.cpp Log: Restructuration/uniformisation/optimisation des structures destin?\195?\169es ?\195?\160 la description des pistes son et autres dans les descriptions des cha?\195?\174nes, afin de simplifier les ?\195?\169changes de ces types de donn?\195?\169es entre les diff?\195?\169rentes parties du programme. Utilisation d'op?\195?\169rateurs surcharg?\195?\169s pour simplifier la d?\195?\169tection des changements dans le contenu du PMT d'une cha?\195?\174ne. D?\195?\169but de r?\195?\169organisation du "grabber" des enregistrements afin de tirer parti des possibilit?\195?\169s offertes par les facilit?\195?\169s d'abstraction et de hi?\195?\169rarchisation du langage C++, et simplifier les ?\195?\169volutions futures de cette partie. Modified: trunk/channels.cpp =================================================================== --- trunk/channels.cpp 2007-12-20 13:35:06 UTC (rev 109) +++ trunk/channels.cpp 2007-12-20 16:14:17 UTC (rev 110) @@ -61,6 +61,55 @@ } +bool liste_pistes::operator == (const liste_pistes & lp) const +{ + if (nbr!=lp.nbr) + return false; + for (int i=0; iMapPID(1, &pid, MEDIA_ELEMENTARY_STREAM); ixSonCourant=0; @@ -379,8 +428,8 @@ hr = pMapMPEG2->MapPID(1, &pid, MEDIA_ELEMENTARY_STREAM); myprintf(L"%?erreur pid video, hr=0x%08x\n", FAILED(hr), hr); - pid = canal_courant.son[ixSonCourant].pid; - if (canal_courant.son[ixSonCourant].type == 1) { + pid = canal_courant.sons.tbl[ixSonCourant].pid; + if (canal_courant.sons.tbl[ixSonCourant].type == 1) { hr = pMapAc3->MapPID(1, &pid, MEDIA_ELEMENTARY_STREAM); myprintf(L"%?erreur pid ac3, hr=0x%08x\n", FAILED(hr), hr); } else { @@ -393,7 +442,11 @@ { IBasicAudio * pBas; - pGraph->QueryInterface(&pBas); - pBas->put_Volume(volume); - pBas->Release(); + HRESULT hr = pGraph->QueryInterface(&pBas); + + myprintf(L"%?erreur set_volume, hr=0x%08x\n", FAILED(hr), hr); + if (SUCCEEDED(hr)) { + pBas->put_Volume(volume); + pBas->Release(); + } } Modified: trunk/channels.h =================================================================== --- trunk/channels.h 2007-12-20 13:35:06 UTC (rev 109) +++ trunk/channels.h 2007-12-20 16:14:17 UTC (rev 110) @@ -38,27 +38,41 @@ SYSTEMTIME fin2; }; -struct un_son { +struct une_piste { WORD pid; - BYTE type; // 0 = normal, 1 = AC3 + BYTE type; // si son : 0 = normal, 1 = AC3; réservé si autres char lang[8]; + + bool operator == (const une_piste & up) const + {return pid==up.pid && type==up.type/* && strcmp(lang, up.lang)==0*/;} + // (strcmp pas nécessaire pour le moment) + + bool operator != (const une_piste & up) const {return ! operator == (up);}; }; -struct un_autre { - WORD pid; - char lang[8]; +struct liste_pistes { + WORD nbr; + une_piste tbl[NB_MAX_PISTES]; + + liste_pistes() : nbr(0) {}; + + bool cherche_pid(WORD pid) const; + int ajoute(WORD newpid, BYTE newtype=0, LPCSTR newlang=NULL); + bool operator == (const liste_pistes & lp) const; + bool operator != (const liste_pistes & lp) const {return ! operator == (lp);}; }; struct ChainePMT { WORD video_pid; WORD mpeg4_pid; WORD pcr_pid; - WORD nb_son; - un_son son[NB_MAX_PISTES]; - WORD nb_autre; - un_autre autre[NB_MAX_PISTES]; + liste_pistes sons; + liste_pistes autr; + ChainePMT(); char norme(void) const; + bool operator == (const ChainePMT & cpmt) const; + bool operator != (const ChainePMT & cpmt) const {return ! operator == (cpmt);}; }; // Définition de l'ensemble des identifiants d'une chaîne : Modified: trunk/grabber.cpp =================================================================== --- trunk/grabber.cpp 2007-12-20 13:35:06 UTC (rev 109) +++ trunk/grabber.cpp 2007-12-20 16:14:17 UTC (rev 110) @@ -31,401 +31,367 @@ #include "main.h" - -/* Constructors */ - -// enregistre en TS -CSampleGrabber::CSampleGrabber(HANDLE hFil) : +/* Constructeur de base */ +CSampleGrabber::CSampleGrabber(HANDLE hFil, int video, int audio) : CUnknown(L"Sample grabber", NULL), - hFile(hFil) + hFile(hFil), + taille_paquet_buf(0), + video_pid(video), + audio_pid(audio) { - pmt_pid = 0; - video_pid = 0; - audio_pid = 0; - - taille_packet_buf = 0; } -// enregistre une chaîne en TS -CSampleGrabber::CSampleGrabber( - HANDLE hFil, int pmt, int video, - const un_son audio[], int nb_audio, - const un_autre other[], int nb_other) : - CUnknown(L"Sample grabber", NULL), - hFile(hFil) +STDMETHODIMP CSampleGrabber::SampleCB(double SampleTime, IMediaSample *pSample) // virtual { - pmt_pid = pmt; - video_pid = video; - nb_aud = nb_audio; - nb_oth = nb_other; - for (int i=0; iGetActualDataLength(); - taille_packet_buf = 0; +// myprintf(L"taille %i\n", l); - myprintf(L"enregistre TS %i %i %i\n", video, audio, pmt_pid); -} + pSample->GetPointer(&p); -// enregistre une chaîne en PS -CSampleGrabber::CSampleGrabber(HANDLE hFil, int video, int audio) : - CUnknown(L"Sample grabber", NULL), - hFile(hFil) -{ - pmt_pid = 0; - video_pid = video; - audio_pid = audio; + long i = 0; - myprintf(L"enregistre PS %i %i\n", video, audio); + // Récupération du fragment de paquet éventuellement reçu à la fin de l'appel précédent : + if (taille_paquet_buf) { + i = 188 - taille_paquet_buf; - taille_vid = 0; - taille_son = 0; + memcpy(&paquet_buf[taille_paquet_buf], p, i); + traite_paquet(paquet_buf, ((paquet_buf[1]&0x1F) << 8 ) + paquet_buf[2]); + taille_paquet_buf = 0; + } - vid_valid = false; - son_valid = false; + // Traitement de tous les paquets entiers reçus ici : + while ( i + 188 <= l){ - pcr_ecrit = false; + if (p[i] == 0x47 && (i+188==l || p[i+188] == 0x47) ) { + traite_paquet(p+i, ((p[i+1]&0x1F) << 8) + p[i+2]); + i += 188; + } else { + // cherche prochaine synchro + BYTE * res = (BYTE *)memchr(&p[i+1], 0x47, l-i-1); - taille_packet_buf = 0; -} + if (res == NULL) { + // rien trouvé, plus rien a faire + return S_OK; + } -/* Destructor */ + myprintf(L"trouve index\n"); -CSampleGrabber::~CSampleGrabber() -{ - myprintf(L"grabber détruit\n"); - CloseHandle(hFile); -} + i = (long)(res - p); + } + } -void CSampleGrabber::pcr_block(BYTE * p) -{ - BYTE pack_header[14]; + // Report du fragment résiduel à la fin pour l'appel suivant : + if (i != l) { + taille_paquet_buf = BYTE(l - i); + memcpy(paquet_buf, &p[i], taille_paquet_buf); + } - memset(pack_header, 0, 14); - pack_header[2]=1; - pack_header[3]=0xBA; + return S_OK; +} - pack_header[11]=0xA0; - pack_header[12]=0x03; - pack_header[13]=0xF8; - // pcr flag - // 01 SCR 32..30 1 SCR 29..28 - // bouffe 5 bits SCR[0] - pack_header[4] = 0x44 + ((p[6]>>2)& 0x38) + ((p[6]>>3) & 3); +STDMETHODIMP CSampleGrabber::BufferCB(double SampleTime, BYTE *pBuffer, long BufferLen) // virtual +{ + myprintf(L"BufferCB\n"); - // SCR 27..20 - // 3 bits SCR[0] et 5 bits SCR[1] - pack_header[5] = ((p[6]<<5)& 0xE0) + ((p[7]>>3)& 0x1F); + return S_OK; +} - // SCR 19..15 1 SCR 14..13 - // 3 bits SCR[1] et 2*2 bits SCR[2] - pack_header[6] = 4 + ((p[7]<<5)& 0xE0) + ((p[8]>>3) & 0x1F) + ((p[8]>>4) & 3); - - // SCR 12..5 - // 4 bits SCR[2] et 4 bits SCR[3] - pack_header[7] = ((p[8]<<4)& 0xF0) + ((p[9]>>4)& 0x0F); - - // SCR 4..0 1 SCR_EXT 8..7 - // 4 bits SCR[3] et 1+2 bit SCR[4] - pack_header[8] = ((p[9]<<4)& 0xF0) + ((p[10]>>4)& 0x08) + 4 + ((p[10]<<1)&2) + ((p[11]>>7)&1); - - // SCR_EXT 6..0 1 - pack_header[9] = ((p[11]<<1)&0xFE) + 1; - +// Sortie d'un bloc de données vers le fichier +BOOL CSampleGrabber::ecrit_fichier(LPCVOID pBuf, DWORD sizBuf) const +{ DWORD written; - WriteFile(hFile, pack_header, 14, &written, NULL); - - pcr_ecrit = true; + return WriteFile(hFile, pBuf, sizBuf, &written, NULL); } -#define TAILLE_PAQUET_MAX 65535 +/* Destructeur général */ -void CSampleGrabber::ecrit_video(void) +CSampleGrabber::~CSampleGrabber() { + myprintf(L"Grabber détruit\n"); + CloseHandle(hFile); +} - DWORD written; +CSampleGrabber_TS::CSampleGrabber_TS( + HANDLE hFil, int pmt, int video, + const liste_pistes & lst_sons, + const liste_pistes & lst_autr) : + CSampleGrabber(hFil, video, 0), + pmt_pid(pmt), + sons(lst_sons), + autr(lst_autr) +{ + myprintf(L"Enregistre TS, video=%i, pmt=%i\n", video, pmt_pid); +} - while (taille_vid - 6 > TAILLE_PAQUET_MAX) { - // on a besoin de decouper +void CSampleGrabber_TS::traite_paquet(BYTE * p, WORD pid) // virtual +{ + // une chaîne en TS + if ( + pid == 0 || + pid == pmt_pid || + pid == video_pid || + sons.cherche_pid(pid) || + autr.cherche_pid(pid) + ) + ecrit_fichier(p, 188); +} - myprintf(L"Paquet trop gros : %i\n", taille_vid - 6); +CSampleGrabber_PS::CSampleGrabber_PS(HANDLE hFil, int video, int audio) : + CSampleGrabber(hFil, video, audio), + taille_vid(0), + vid_valid(false), + taille_son(0), + son_valid(false), + pcr_ecrit(false) +{ + myprintf(L"Enregistre PS, video=%i, audio=%i\n", video, audio); +} +void CSampleGrabber_PS::traite_paquet(BYTE * p, WORD pid) // virtual +{ + // une chaîne en PS - int index = TAILLE_PAQUET_MAX + 6; + if (pid == video_pid) { - // copie autant qu'on peut + // si la video est valide, on verifie que pas de discontinuité + if (vid_valid) { + int current = p[3]&15; - int taille_sous_paquet = index - 6; + if ( ( (current - vid_continuity_counter) & 15 ) != 1 ) { + myprintf(L"discontinuite dans la video\n"); - vid_buf[4] = (BYTE)(taille_sous_paquet >> 8); - vid_buf[5] = (BYTE) taille_sous_paquet; + vid_valid = false; - WriteFile(hFile, vid_buf, index, &written, NULL); + // jette le paquet qu'on a + if (taille_vid && pcr_ecrit) { + ecrit_video(); + } - taille_vid -= index; + taille_vid = 0; + } + } - memcpy(&vid_buf[9], &vid_buf[index], taille_vid); - taille_vid += 9; + if (p[1] & 0x40) { + // start indicator - vid_buf[6] &= 0xFB; - vid_buf[7] = 0; - vid_buf[8] = 0; + vid_valid = true; - } - - - long taille = taille_vid - 6; - - vid_buf[4] = (BYTE)(taille >> 8); - vid_buf[5] = (BYTE) taille; - - WriteFile(hFile, vid_buf, taille_vid, &written, NULL); - -} - -void CSampleGrabber::traite_packet(BYTE * p) -{ - DWORD written; - int pid = ( (p[1]&0x1F) << 8 ) + p[2]; - - if (pmt_pid) { - // une chaîne en TS - - bool trouve=0; - - for (int i=0; i> 8); + son_buf[5] = (BYTE) taille; - if (vid_valid) { - vid_continuity_counter = p[3]&15; + ecrit_fichier(son_buf, taille_son); + } - if (p[3] & 0x10) { - // il y a du payload + taille_son = 0; + } + } - if (p[3] & 0x20) { - // et adaptation field - unsigned int taille = p[4]+1; + if (p[1] & 0x40) { + // start indicator - if (taille < 184) { - unsigned int taille_copie = 184-taille; + son_valid = true; - if (taille_vid + taille_copie <= sizeof(vid_buf) ) { - memcpy(&vid_buf[taille_vid], &p[4+taille], taille_copie); - taille_vid += taille_copie; - } + if (taille_son && pcr_ecrit) { + // on sauve ce qu'on a en stock - if (p[5] & 0x10) { - pcr_block(p); - } - } - } else { - if (taille_vid + 184 <= sizeof(vid_buf) ) { - memcpy(&vid_buf[taille_vid], &p[4], 184); - taille_vid += 184; - } + long taille = taille_son - 6; - } - } + son_buf[4] = (BYTE)(taille >> 8); + son_buf[5] = (BYTE) taille; + + ecrit_fichier(son_buf, taille_son); } - } else if (pid == audio_pid) { - // si le son est valide, on verifie que pas de discontinuité - if (son_valid) { - int current = p[3]&15; + taille_son = 0; - if ( ( (current - son_continuity_counter) & 15 ) != 1 ) { - myprintf(L"discontinuite dans le son\n"); + } - son_valid = false; + if (son_valid) { + son_continuity_counter = p[3]&15; - // jette le paquet qu'on a - if (taille_son && pcr_ecrit) { - // on sauve ce qu'on a en stock + if (p[3] & 0x10) { + // il y a du payload - long taille = taille_son - 6; + if (p[3] & 0x20) { + // et adaptation field + unsigned int taille = p[4]+1; - son_buf[4] = (BYTE)(taille >> 8); - son_buf[5] = (BYTE) taille; + if (taille < 184) { + unsigned int taille_copie = 184-taille; - WriteFile(hFile, son_buf, taille_son, &written, NULL); + if (taille_son + taille_copie <= sizeof(son_buf) ) { + memcpy(&son_buf[taille_son], &p[4+taille], taille_copie); + taille_son += taille_copie; + } } - - taille_son = 0; + } else { + if (taille_son + 184 <= sizeof(son_buf) ) { + memcpy(&son_buf[taille_son], &p[4], 184); + taille_son += 184; + } } } + } - if (p[1] & 0x40) { - // start indicator + } +} - son_valid = true; +void CSampleGrabber_PS::pcr_block(BYTE * p) +{ + BYTE pack_header[14]; - if (taille_son && pcr_ecrit) { - // on sauve ce qu'on a en stock + memset(pack_header, 0, 14); + pack_header[2]=1; + pack_header[3]=0xBA; - long taille = taille_son - 6; + pack_header[11]=0xA0; + pack_header[12]=0x03; + pack_header[13]=0xF8; - son_buf[4] = (BYTE)(taille >> 8); - son_buf[5] = (BYTE) taille; + // pcr flag + // 01 SCR 32..30 1 SCR 29..28 + // bouffe 5 bits SCR[0] + pack_header[4] = 0x44 + ((p[6]>>2)& 0x38) + ((p[6]>>3) & 3); - WriteFile(hFile, son_buf, taille_son, &written, NULL); - } + // SCR 27..20 + // 3 bits SCR[0] et 5 bits SCR[1] + pack_header[5] = ((p[6]<<5)& 0xE0) + ((p[7]>>3)& 0x1F); - taille_son = 0; + // SCR 19..15 1 SCR 14..13 + // 3 bits SCR[1] et 2*2 bits SCR[2] + pack_header[6] = 4 + ((p[7]<<5)& 0xE0) + ((p[8]>>3) & 0x1F) + ((p[8]>>4) & 3); - } + // SCR 12..5 + // 4 bits SCR[2] et 4 bits SCR[3] + pack_header[7] = ((p[8]<<4)& 0xF0) + ((p[9]>>4)& 0x0F); - if (son_valid) { - son_continuity_counter = p[3]&15; + // SCR 4..0 1 SCR_EXT 8..7 + // 4 bits SCR[3] et 1+2 bit SCR[4] + pack_header[8] = ((p[9]<<4)& 0xF0) + ((p[10]>>4)& 0x08) + 4 + ((p[10]<<1)&2) + ((p[11]>>7)&1); - if (p[3] & 0x10) { - // il y a du payload + // SCR_EXT 6..0 1 + pack_header[9] = ((p[11]<<1)&0xFE) + 1; - if (p[3] & 0x20) { - // et adaptation field - unsigned int taille = p[4]+1; + ecrit_fichier(pack_header, 14); - if (taille < 184) { - unsigned int taille_copie = 184-taille; - - if (taille_son + taille_copie <= sizeof(son_buf) ) { - memcpy(&son_buf[taille_son], &p[4+taille], taille_copie); - taille_son += taille_copie; - } - } - } else { - if (taille_son + 184 <= sizeof(son_buf) ) { - memcpy(&son_buf[taille_son], &p[4], 184); - taille_son += 184; - } - } - } - } - - } - - } else { - // full TS - if ( pid != 0x1FFF ) { - WriteFile(hFile, p, 188, &written, NULL); - } - } - + pcr_ecrit = true; } -STDMETHODIMP CSampleGrabber::SampleCB(double SampleTime, IMediaSample *pSample) +CSampleGrabber_Stream::CSampleGrabber_Stream(HANDLE hFil) : + CSampleGrabber(hFil, 0, 0) { + myprintf(L"Enregistre multiplex\n"); +} - BYTE * p; - long l = pSample->GetActualDataLength(); +#define TAILLE_PAQUET_MAX 65535 -// myprintf(L"taille %i\n", l); +void CSampleGrabber_PS::ecrit_video(void) +{ + while (taille_vid - 6 > TAILLE_PAQUET_MAX) { + // on a besoin de decouper - pSample->GetPointer(&p); + myprintf(L"Paquet trop gros : %i\n", taille_vid - 6); - long i = 0; - if (taille_packet_buf) { - i = 188 - taille_packet_buf; + int index = TAILLE_PAQUET_MAX + 6; - memcpy(&packet_buf[taille_packet_buf], p, i); + // copie autant qu'on peut - traite_packet(packet_buf); + int taille_sous_paquet = index - 6; - taille_packet_buf = 0; - } + vid_buf[4] = (BYTE)(taille_sous_paquet >> 8); + vid_buf[5] = (BYTE) taille_sous_paquet; - while ( i + 188 <= l){ + ecrit_fichier(vid_buf, index); - if (p[i] == 0x47 && (i+188==l || p[i+188] == 0x47) ) { - traite_packet(&p[i]); - i += 188; - } else { - // cherche prochaine synchro + taille_vid -= index; - BYTE * res = (BYTE *)memchr(&p[i+1], 0x47, l-i-1); - if (res == NULL) { - // rien trouvé, plus rien a faire - return S_OK; - } + memcpy(&vid_buf[9], &vid_buf[index], taille_vid); - myprintf(L"trouve index\n"); + taille_vid += 9; - i = (long)(res - p); - } + vid_buf[6] &= 0xFB; + vid_buf[7] = 0; + vid_buf[8] = 0; - } - if (i != l) { - taille_packet_buf = l - i; - memcpy(packet_buf, &p[i], taille_packet_buf); - } + long taille = taille_vid - 6; - return S_OK; -} + vid_buf[4] = (BYTE)(taille >> 8); + vid_buf[5] = (BYTE) taille; + ecrit_fichier(vid_buf, taille_vid); -STDMETHODIMP CSampleGrabber::BufferCB(double SampleTime, BYTE *pBuffer, long BufferLen) -{ - myprintf(L"BufferCB\n"); +} - return S_OK; +void CSampleGrabber_Stream::traite_paquet(BYTE * p, WORD pid) // virtual +{ + // full TS + if ( pid != 0x1FFF ) { + ecrit_fichier(p, 188); + } } Modified: trunk/grabber.h =================================================================== --- trunk/grabber.h 2007-12-20 13:35:06 UTC (rev 109) +++ trunk/grabber.h 2007-12-20 16:14:17 UTC (rev 110) @@ -29,54 +29,34 @@ #include "channels.h" +/* Description Transport stream packet + + Syntax No. of bits Offset + ------------------------------------------------------ + transport_packet() { + sync_byte 8 0 + transport_error_indicator 1 1 + payload_unit_start_indicator 1 1 + transport_priority 1 1 + PID 13 1, 2 + transport_scrambling_control 2 3 + adaptation_field_control 2 3 + continuity_counter 4 3 + if(adaptation_field_control=='10'||adaptation_field_control=='11') { + adaptation_field() + } + if (adaptation_field_control=='01'||adaptation_field_control=='11') { + for (i=0;i Multiplex - CSampleGrabber(HANDLE hFil, int video, int audio); - CSampleGrabber::CSampleGrabber( - HANDLE hFil, int pmt, int video, - const un_son audio[], int nb_audio, - const un_autre other[], int nb_other); - - ~CSampleGrabber(); - // // --- ISampleGrabberCB methods --- // @@ -100,4 +80,67 @@ return CUnknown::NonDelegatingQueryInterface(riid, ppv); } + BYTE taille_paquet_buf; + BYTE paquet_buf[188]; + +protected: + + int video_pid, audio_pid; + + virtual void traite_paquet(BYTE * p, WORD pid) = 0; + + CSampleGrabber(HANDLE hFil, int video, int audio); + + // Sortie d'un bloc de données vers le fichier + BOOL ecrit_fichier(LPCVOID pBuf, DWORD sizBuf) const; + + ~CSampleGrabber(); + }; + +class CSampleGrabber_TS : public CSampleGrabber +{ + int pmt_pid; + liste_pistes sons; + liste_pistes autr; + + virtual void traite_paquet(BYTE * p, WORD pid); + +public: + + CSampleGrabber_TS( HANDLE hFil, int pmt, int video, + const liste_pistes & lst_sons, + const liste_pistes & lst_autr); +}; + +class CSampleGrabber_PS : public CSampleGrabber +{ + BYTE vid_buf[256*1024]; + long taille_vid; + bool vid_valid; + int vid_continuity_counter; + + BYTE son_buf[64*1024]; + long taille_son; + bool son_valid; + int son_continuity_counter; + + bool pcr_ecrit; + + void pcr_block(BYTE * p); + + void ecrit_video(void); + + virtual void traite_paquet(BYTE * p, WORD pid); + +public: + CSampleGrabber_PS(HANDLE hFil, int video, int audio); +}; + +class CSampleGrabber_Stream : public CSampleGrabber // -> Multiplex +{ + virtual void traite_paquet(BYTE * p, WORD pid); + +public: + CSampleGrabber_Stream(HANDLE hFil); +}; Modified: trunk/ini.cpp =================================================================== --- trunk/ini.cpp 2007-12-20 13:35:06 UTC (rev 109) +++ trunk/ini.cpp 2007-12-20 16:14:17 UTC (rev 110) @@ -236,7 +236,7 @@ // Détermine si l'émetteur choisi existe. // - // S'il n'exite pas, scanne toutes les fréquences, entre les canaux 21 et 69 + // S'il n'existe pas, scanne toutes les fréquences, entre les canaux 21 et 69 freq.canal_no = (WORD)GetPrivateProfileInt(ville, L"R1", 0, fileName); if (freq.canal_no == 0) { freq_scan.clear(); @@ -569,9 +569,9 @@ canal.video_pid = (WORD)pNode->getInt(L"Video"); canal.mpeg4_pid = (WORD)pNode->getInt(L"Video_mpeg4"); - canal.nb_son = (WORD)pNode->getInt(L"Nb_Son"); - for (int i=0;igetInt(L"Nb_Son"); + for (int i=0;igetInt(var); @@ -582,9 +582,9 @@ swprintf_s(var, _countof(var), L"Son%i_Lang", i); pNode->getStr(var, canal_son.lang, _countof(canal_son.lang)); } - canal.nb_autre = (WORD)pNode->getInt(L"Nb_Autre"); - for (int i=0;igetInt(L"Nb_Autre"); + for (int i=0;igetInt(var); @@ -646,20 +646,20 @@ fprintf(fop, "\t\t%i\n", canal.mpeg4_pid); } - fprintf(fop, "\t\t%i\n", canal.nb_son); + fprintf(fop, "\t\t%i\n", canal.sons.nbr); - for (int j=0; j%i\n", j, canal_son.pid, j); fprintf(fop, "\t\t%i\n", j, canal_son.type, j); fprintf(fop, "\t\t%s\n", j, canal_son.lang, j); } - fprintf(fop, "\t\t%i\n", canal.nb_autre); + fprintf(fop, "\t\t%i\n", canal.autr.nbr); - for (int j=0; j%i\n", j, canal_autre.pid, j); fprintf(fop, "\t\t%s\n", j, canal_autre.lang, j); Modified: trunk/main.cpp =================================================================== --- trunk/main.cpp 2007-12-20 13:35:06 UTC (rev 109) +++ trunk/main.cpp 2007-12-20 16:14:17 UTC (rev 110) @@ -530,10 +530,10 @@ HMENU pistes = CreatePopupMenu(); - for (int i=0; iGetPages(&caGUID); if (SUCCEEDED(hr)) { - LCID lcid = GetUserDefaultLCID(); + LCID lcid = GetUserDefaultLCID(); - ShowCursor(TRUE); + ShowCursor(TRUE); - hr = OleCreatePropertyFrame( - hMainWnd, - 10, - 10, - nom, - 1, - &pUnk, - caGUID.cElems, - caGUID.pElems, - lcid, - 0L, - NULL ); + hr = OleCreatePropertyFrame( + hMainWnd, + 10, + 10, + nom, + 1, + &pUnk, + caGUID.cElems, + caGUID.pElems, + lcid, + 0L, + NULL ); - ShowCursor(FALSE); + ShowCursor(FALSE); CoTaskMemFree(caGUID.pElems); - if (FAILED(hr)) { + if (FAILED(hr)) { erreur(L"Erreur lors de la création de la page de propriétés"); } - } + } - pUnk->Release(); + pUnk->Release(); } pPages->Release(); @@ -1755,7 +1755,7 @@ // Comme le numéro de la chaine et la chaine sont au même endroit, // n'affiche que le plus récent des deux : if (show_prog && show_chaine) { - if(last_chaine > last_zapping) + if (last_chaine > last_zapping) show_prog = false; else show_chaine = false; Modified: trunk/parse.cpp =================================================================== --- trunk/parse.cpp 2007-12-20 13:35:06 UTC (rev 109) +++ trunk/parse.cpp 2007-12-20 16:14:17 UTC (rev 110) @@ -247,8 +247,8 @@ void parse_pmt(const PMT * ppmt, int max, ChainePMT & pmt/*, bool debug*/) { pmt.pcr_pid = ppmt->pcr_pid(); - pmt.nb_son=0; - pmt.nb_autre=0; + pmt.sons.nbr=0; + pmt.autr.nbr=0; for (const PMT_entry * ppent = ppmt->begin(); LPBYTE(ppent)end(max); ppent=ppent->next()) { WORD pid = ppent->pid(); @@ -266,9 +266,7 @@ break; case 0x03: //ISO_IEC_11172_3_AUDIO, MPEG-1 audio streams case 0x04: //ISO_IEC_13818_3_AUDIO, MPEG-2 audio streams - pmt.son[pmt.nb_son].pid=pid; - pmt.son[pmt.nb_son].type=0; - pmt.nb_son++; + pmt.sons.ajoute(pid, 0); break; case 0x06: private_stream=1; @@ -280,16 +278,12 @@ if (private_stream) { for (; (LPBYTE)ppdescend(); ppdesc=ppdesc->next()) { - if (ppdesc->descriptor_tag == 0x6A) { - un_son & pmt_son = pmt.son[pmt.nb_son++]; - pmt_son.pid=pid; - pmt_son.type=1; - } + if (ppdesc->descriptor_tag == 0x6A) + pmt.sons.ajoute(pid, 1); } - if (pmt.son[pmt.nb_son-1].pid != pid) { - pmt.autre[pmt.nb_autre++].pid=pid; - } + if (pmt.sons.tbl[pmt.sons.nbr-1].pid != pid) + pmt.autr.ajoute(pid); ppdesc = ppent->begin(); } @@ -301,7 +295,7 @@ //if (debug) // myprintf(L"sous_type : %02X, sous_taille : %i\n", tag, length); if (tag == 0x0A && length > 0) { - un_son & pmt_son = pmt.son[pmt.nb_son-1]; + une_piste & pmt_son = pmt.sons.tbl[pmt.sons.nbr-1]; if (pmt_son.pid == pid) { memcpy(pmt_son.lang, ppdesc->begin(), length); @@ -309,7 +303,7 @@ pmt_son.lang[length-1]='\0'; } - // un_autre & pmt_autre = pmt.autre[pmt.nb_autre-1]; + // une_piste & pmt_autre = pmt.autr.tbl[pmt.autr.nbr-1]; // if (pmt_autre.pid == pid) { // memcpy(pmt_autre.lang, ppdesc->begin(), length); Modified: trunk/pmtfilter.cpp =================================================================== --- trunk/pmtfilter.cpp 2007-12-20 13:35:06 UTC (rev 109) +++ trunk/pmtfilter.cpp 2007-12-20 16:14:17 UTC (rev 110) @@ -49,79 +49,24 @@ static void verifie_pmt(unsigned char * p, int max) { + ChainePMT & chaine = Canaux[ixChaineCourante]; ChainePMT pmt; - pmt.video_pid = 0; - pmt.mpeg4_pid = 0; - - for (int i=0; i Author: gingko Date: 2007-12-21 13:04:57 +0100 (Fri, 21 Dec 2007) New Revision: 111 Modified: trunk/grabber.cpp trunk/grabber.h Log: Correctif : remplacement de la fonction 'traite_paquet' "pure" que j'avais d?\195?\169finie dans la nouvelle version de CSampleGrabber par une fonction existante mais vide car il appara?\195?\174t que cette fonction peut ?\195?\170tre appel?\195?\169e transitoirement pendant la destruction du "grabber", en raison de la r?\195?\169ception d'?\195?\169chantillons r?\195?\169siduels, causant des erreurs run-time ?\195?\160 l'arr?\195?\170t des enregistrements. Modified: trunk/grabber.cpp =================================================================== --- trunk/grabber.cpp 2007-12-20 16:14:17 UTC (rev 110) +++ trunk/grabber.cpp 2007-12-21 12:04:57 UTC (rev 111) @@ -108,8 +108,12 @@ return WriteFile(hFile, pBuf, sizBuf, &written, NULL); } +// (peut-être appelé transitoirement pendant la destruction) : +void CSampleGrabber::traite_paquet(BYTE * p, WORD pid) // virtual +{ +} + /* Destructeur général */ - CSampleGrabber::~CSampleGrabber() { myprintf(L"Grabber détruit\n"); Modified: trunk/grabber.h =================================================================== --- trunk/grabber.h 2007-12-20 16:14:17 UTC (rev 110) +++ trunk/grabber.h 2007-12-21 12:04:57 UTC (rev 111) @@ -87,7 +87,7 @@ int video_pid, audio_pid; - virtual void traite_paquet(BYTE * p, WORD pid) = 0; + virtual void traite_paquet(BYTE * p, WORD pid); CSampleGrabber(HANDLE hFil, int video, int audio); From pouchintv-svn at baysse.fr Fri Dec 14 17:04:17 2007 From: pouchintv-svn at baysse.fr (pouchintv-svn at baysse.fr) Date: Fri, 14 Dec 2007 16:04:17 -0000 Subject: [Pouchintv-dev] [PouchinTVMod] gingko | r106 - trunk Message-ID: <20071214160359.C6C375F2FF@mail.baysse.fr> Author: gingko Date: 2007-12-14 17:03:59 +0100 (Fri, 14 Dec 2007) New Revision: 106 Modified: trunk/base.h trunk/channels.cpp trunk/epg.cpp trunk/grabber.cpp trunk/grabber.h trunk/ini.cpp trunk/ini.h trunk/main.cpp trunk/main.h trunk/record.cpp trunk/record.h trunk/recprog.cpp trunk/recprog.h trunk/res.rc trunk/resource.h trunk/settings.cpp Log: R?\195?\169tablissement de la fonction d'enregistrement du multiplex. L'utilisation de celle-ci est cependant maintenant subordonn?\195?\169 ?\195?\160 une activation dans le dialogue de configuration, afin que l'utilisateur d?\195?\169butant ne soit pas concern?\195?\169 par cette surcharge des menus. Lorsque des cha?\195?\174nes autres que la cha?\195?\174ne courante sont enregistr?\195?\169es, des items de menu suppl?\195?\169mentaires sont ajout?\195?\169s pour les stopper. L'arr?\195?\170t d'enregistrement retard?\195?\169 est maintenant compl?\195?\168tement int?\195?\169gr?\195?\169 au syst?\195?\168me d'enregistrements programm?\195?\169s, et rendu plus complet : - il peut concerner toutes les cha?\195?\174nes en cours d'enregistrement. - il peut ?\195?\170tre modifi?\195?\169 ou bien annul?\195?\169 une fois lanc?\195?\169. - il peut servir ?\195?\160 v?\195?\169rifier le temps restant (d?\195?\169compte affich?\195?\169). - il peut servir ?\195?\160 modifier la temporisation d'un enregistrement d?\195?\169j?\195?\160 d?\195?\169marr?\195?\169 par programmation (dans ce cas, la nouvelle heure d'arr?\195?\170t est report?\195?\169e dans la programmation, sauf si celle-ci inclut des r?\195?\169p?\195?\169titions). - il peut ?\195?\170tre d?\195?\169fini aussi bien par temps restant que par heure d'arr?\195?\170t. Les enregistrements programm?\195?\169s sont ?\195?\169galement r?\195?\169vis?\195?\169s : - gestion imm?\195?\169diate des modifications (leur validation diff?\195?\169r?\195?\169e est difficilement compatible avec la gestion "modeless" du dialogue introduite il y a un certain temps d?\195?\169j?\195?\160). - on peut sp?\195?\169cifier une heure de d?\195?\169but dans le pass?\195?\169 (dans ce cas, l'enregistrement d?\195?\169marre imm?\195?\169diatement). - un bouton "Nouveau" a ?\195?\169t?\195?\169 ajout?\195?\169 pour r?\195?\169initialiser les donn?\195?\169es en vue d'une nouvelle programmation. - un bouton "?\195?\137mission en cours" a ?\195?\169t?\195?\169 ajout?\195?\169 pour charger les horaires de l'?\195?\169mission en cours de diffusion. - la liste est affich?\195?\169e tri?\195?\169e par date/heure de d?\195?\169but. - les programmations peuvent ?\195?\170tre d?\195?\169sactiv?\195?\169es. - un test a ?\195?\169t?\195?\169 ajout?\195?\169 pour limiter la dur?\195?\169e d'un enregistrement programm?\195?\169 ?\195?\160 12 heures. - un test a ?\195?\169t?\195?\169 ajout?\195?\169 pour emp?\195?\170cher la programmation d'une ?\195?\169mission de dur?\195?\169e nulle. - un test a ?\195?\169t?\195?\169 ajout?\195?\169 pour emp?\195?\170cher deux enregistrements d'avoir lieu en m?\195?\170me temps sur la m?\195?\170me cha?\195?\174ne. - une colonne d'?\195?\169tat de l'enregistrement a ?\195?\169t?\195?\169 ajout?\195?\169e dans la liste (actif, inactif, termin?\195?\169, interrompu, supplant?\195?\169, erreur). - les donn?\195?\169es de la liste, et en particulier cette colonne d'?\195?\169tat, sont mises ?\195?\160 jour en temps r?\195?\169el. - l'affichage de l'heure de d?\195?\169but et de l'heure de fin a ?\195?\169t?\195?\169 rendu plus convivial. - les enregistrements avec r?\195?\169p?\195?\169tition sont signal?\195?\169s par une "*" ajout?\195?\169e apr?\195?\168s l'heure de fin. - le calcul des r?\195?\169p?\195?\169titions est effectu?\195?\169 apr?\195?\168s l'heure de fin, et non plus au d?\195?\169marrage de l'enregistrement (gestion plus coh?\195?\169rente, surtout en cas de plantage). - un nom d'enregistrement est g?\195?\169n?\195?\169r?\195?\169 automatiquement (?\195?\160 partir du nom de la cha?\195?\174ne, du jour du mois et du mois, plus un indice si ceci produirait un doublon) si aucun n'a ?\195?\169t?\195?\169 saisi. Ce nom automatique n'est pas repris en cas de modification. - le nom de l'enregistrement est ajout?\195?\169 au d?\195?\169but du nom du fichier d'enregistrement s'il n'avait pas ?\195?\169t?\195?\169 g?\195?\169n?\195?\169r?\195?\169 automatiquement. - les contr?\195?\180les d'heures/minutes/secondes peuvent ?\195?\170tre incr?\195?\169ment?\195?\169s ou d?\195?\169cr?\195?\169ment?\195?\169s avec report de la retenue sur l'unit?\195?\169 sup?\195?\169rieure. - le dialogue lui-m?\195?\170me a ?\195?\169t?\195?\169 r?\195?\169arrang?\195?\169. Le timer correspondant ?\195?\160 l'enregistrement (il n'y en a plus qu'un seul) est automatiquement recalcul?\195?\169 en cas de modification ext?\195?\169rieure de l'heure syst?\195?\168me. La dur?\195?\169e de ce timer est ?\195?\169galement plafonn?\195?\169e (actuellement ?\195?\160 10 minutes), provoquant son recalcul p?\195?\169riodique ?\195?\160 cet intervalle. Modified: trunk/base.h =================================================================== --- trunk/base.h 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/base.h 2007-12-14 16:03:59 UTC (rev 106) @@ -74,9 +74,6 @@ // récupérer avec un debugger ou bien un logiciel tel que DebugView : // http://www.microsoft.com/TechNet/Sysinternals/Utilities/DebugView.mspx -// Décommenter si on veut réactiver l'enregistrement du multiplex : -//#define USE_RECORD_STREAM 1 - #define EXPORT_GRAPH 0 // Activer le LCD Logitech Modified: trunk/channels.cpp =================================================================== --- trunk/channels.cpp 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/channels.cpp 2007-12-14 16:03:59 UTC (rev 106) @@ -147,9 +147,7 @@ WORD indexToSID(int ixChaine) { return -#if USE_RECORD_STREAM ixChaine==STREAM_PSEUDO_INDEX ? STREAM_PSEUDO_SID : -#endif ixChaine_ok(ixChaine) ? Canaux[ixChaine].SID : 0; } Modified: trunk/epg.cpp =================================================================== --- trunk/epg.cpp 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/epg.cpp 2007-12-14 16:03:59 UTC (rev 106) @@ -98,34 +98,6 @@ } } -// Programmation d'un enregistrement : -int epg_add_programme(int canalIdx, int noemis, MethodeEnregistrement methode, AudioMode audio) -{ - const Chaine & canal = Canaux[canalIdx]; - const Emission & emis = canal.emis[noemis]; - Programme prog = { - methode, // methode - audio, // audio - apr_rien, // after - planif_sans, // tache - emis.debut2, // debut - emis.fin2, // fin - canal.numeroChaine, // numeroChaine - canal.SID // sidChaine - // - // tout le reste à zéro (implicite) - }; - - strcpy_s(prog.nom, _countof(prog.nom), emis.nom); - if (!prog.verifie()) - return 1; - - Programmes.insert(Programmes.begin() + Programmes.size(), prog); - update_record_list(); - sauve_programmes(); - return 0; // 0 = pas d'erreur -} - INT_PTR CALLBACK EpgDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { static HWND hListItem = NULL; @@ -162,19 +134,10 @@ } remplit_table_epg(hListItem); + remplit_liste_methodes(GetDlgItem(hDlg, IDC_METHOD_EPG)); + remplit_liste_audio(GetDlgItem(hDlg, IDC_AUDIO_EPG)); + EnableWindow(GetDlgItem(hDlg, IDC_AUDIO_EPG), FALSE); - SendDlgItemMessage(hDlg, IDC_METHOD_EPG, CB_ADDSTRING, 0, (LPARAM)TEXT("TS")); - SendDlgItemMessage(hDlg, IDC_METHOD_EPG, CB_ADDSTRING, 0, (LPARAM)TEXT("PS")); -#if USE_RECORD_STREAM - SendDlgItemMessage(hDlg, IDC_METHOD_EPG, CB_ADDSTRING, 0, (LPARAM)TEXT("Multiplex")); -#endif - SendDlgItemMessage(hDlg, IDC_METHOD_EPG, CB_SETCURSEL, 0, 0); - SendDlgItemMessage(hDlg, IDC_AUDIO_EPG, CB_ADDSTRING, 0, (LPARAM)TEXT("Audio 1")); - SendDlgItemMessage(hDlg, IDC_AUDIO_EPG, CB_ADDSTRING, 0, (LPARAM)TEXT("Audio 2")); - SendDlgItemMessage(hDlg, IDC_AUDIO_EPG, CB_ADDSTRING, 0, (LPARAM)TEXT("Audio AC3")); - SendDlgItemMessage(hDlg, IDC_AUDIO_EPG, CB_SETCURSEL, 0, 0); - EnableWindow(GetDlgItem( hDlg, IDC_AUDIO_EPG), FALSE); - ShowCursor(TRUE); // Suspend la disparition temporisée du curseur return TRUE; } @@ -213,10 +176,15 @@ ListView_GetItem(hListItem, &item); int ixChaine = trouve_chaine_par_sid(LOWORD(item.lParam)); - MethodeEnregistrement methode = (MethodeEnregistrement)SendDlgItemMessage(hDlg, IDC_METHOD_EPG, CB_GETCURSEL, 0, 0); - AudioMode audio = (AudioMode)SendDlgItemMessage(hDlg, IDC_AUDIO_EPG, CB_GETCURSEL, 0, 0); + Programme prog; - epg_add_programme(ixChaine, LOBYTE(HIWORD(item.lParam)), methode, audio); + if (!chaineToProg(ixChaine, LOBYTE(HIWORD(item.lParam)), prog) || !prog.verifie(false)) + return FALSE; + prog.methode = (MethodeEnregistrement)SendDlgItemMessage(hDlg, IDC_METHOD_EPG, CB_GETCURSEL, 0, 0); + prog.audio = (AudioMode)SendDlgItemMessage(hDlg, IDC_AUDIO_EPG, CB_GETCURSEL, 0, 0); + + Programmes.push_back(prog); + finalize_prog_change(); return TRUE; } } return FALSE; Modified: trunk/grabber.cpp =================================================================== --- trunk/grabber.cpp 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/grabber.cpp 2007-12-14 16:03:59 UTC (rev 106) @@ -35,7 +35,6 @@ /* Constructors */ // enregistre en TS -#if USE_RECORD_STREAM CSampleGrabber::CSampleGrabber(HANDLE hFil) : CUnknown(L"Sample grabber", NULL), hFile(hFil) @@ -46,7 +45,6 @@ taille_packet_buf = 0; } -#endif // #if USE_RECORD_STREAM // enregistre une chaîne en TS CSampleGrabber::CSampleGrabber( @@ -97,7 +95,7 @@ CSampleGrabber::~CSampleGrabber() { - myprintf(L"detruit\n"); + myprintf(L"grabber détruit\n"); CloseHandle(hFile); } Modified: trunk/grabber.h =================================================================== --- trunk/grabber.h 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/grabber.h 2007-12-14 16:03:59 UTC (rev 106) @@ -68,9 +68,7 @@ public: -#if USE_RECORD_STREAM - CSampleGrabber(HANDLE hFil); -#endif // #if USE_RECORD_STREAM + CSampleGrabber(HANDLE hFil); // -> Multiplex CSampleGrabber(HANDLE hFil, int video, int audio); CSampleGrabber::CSampleGrabber( HANDLE hFil, int pmt, int video, Modified: trunk/ini.cpp =================================================================== --- trunk/ini.cpp 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/ini.cpp 2007-12-14 16:03:59 UTC (rev 106) @@ -35,7 +35,9 @@ #include "xml.h" #include +#include // pour 'sort' +// Nom de base du fichier définissant les villes et les canaux wchar_t scan_ini[]=L"canaux.ini"; static wchar_t general[]=L"Général"; @@ -45,7 +47,10 @@ WindowPos consolePos; #endif // #if USE_CONSOLE==1 +// Priorité du processus courant selon la configuration DWORD configPriority = IDM_ABOVE_NORMAL_PRIORITY; + +// État de la fenêtre principale selon la configuration EtatsFenetre configState = etf_normal; #define freq_canal(x) (x * 8000 + freq_offset) @@ -75,7 +80,7 @@ static wchar_t ini_ac3_defaut[] = L"Utilise AC3 quand dispo"; static wchar_t ini_suspend_minimized[] = L"Suspendre si minimisé"; static wchar_t ini_minimize_system_tray[] = L"Minimiser dans le system tray"; - +static wchar_t ini_allow_stream_record[] = L"Enregistrement multiplex autorisé"; static wchar_t ini_use_all_width[] = L"Utilise toute la largeur"; static wchar_t ini_etirer_video[] = L"Étirer la vidéo"; static wchar_t ini_zoom_ratio[] = L"Facteur de zoom"; @@ -113,10 +118,13 @@ { LPSTR pcar; - strcpy_s(nom, _countof(nom), src); + if (src!=NULL) { + strcpy_s(nom, _countof(nom), src); - while ((pcar = strpbrk(nom, "\"*/:<>?\\|")) != NULL) - *pcar = remplacement; + while ((pcar = strpbrk(nom, "\"*/:<>?\\|")) != NULL) + *pcar = remplacement; + } else + nom[0] = 0; } // Extraction d'une copie Unicode : @@ -126,18 +134,22 @@ nom, -1, lpWideCharStr, cchWideChar); } -NomFichierAvecDate::NomFichierAvecDate(LPCWSTR dir, LPCSTR prefixe, LPCSTR extension) +NomFichierAvecDate::NomFichierAvecDate(LPCWSTR dir, LPCSTR prefixe1, LPCSTR prefixe2, LPCSTR extension) { - NomProtege prefix_prot(prefixe, ' '); - SYSTEMTIME st; + NomProtege prefix_prot1(prefixe1, ' '); + NomProtege prefix_prot2(prefixe2, ' '); + SYSTEMTIME localtime; - GetLocalTime(&st); + GetLocalTime(&localtime); swprintf_s( nom, _countof(nom), - L"%s\\%S %04i-%02i-%02i %02i-%02i-%02i.%S", - dir, prefix_prot.nom, - st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, + L"%s\\%S%S%S %04i-%02i-%02i %02i-%02i-%02i.%S", + dir, prefix_prot1.nom, + prefix_prot1.nom[0]!=0 && prefix_prot2.nom[0]!=0 ? " - " : "", + prefix_prot2.nom, + localtime.wYear, localtime.wMonth, localtime.wDay, + localtime.wHour, localtime.wMinute, localtime.wSecond, extension); } @@ -250,7 +262,10 @@ } } -static LPCWSTR Tbl_DoW[7] = { +/** + * Table des jours de la semaine pour sauvegarde et affichage. + **/ +LPCWSTR Tbl_DoW[7] = { L"Dimanche", L"Lundi", L"Mardi", @@ -281,6 +296,9 @@ stim.wSecond = (WORD)pNode->getInt(xstn.name_wSecond); } +/** + * Lecture des programmations d'enregistrement depuis un fichier. + **/ int lit_programmes(void) { static const XMLSystemTimeNames Xstn_std = { @@ -335,22 +353,20 @@ CXMLNode * pNode = children[i]; Programme prog; - memset(&prog, 0, sizeof(Programme)); pNode->getStr(L"Nom", prog.nom, _countof(prog.nom)); - + prog.nomAuto = pNode->getInt(L"NomAuto")!=0; + prog.sidChaine = (WORD)pNode->getInt(L"ServiceID"); prog.numeroChaine = (WORD)pNode->getInt(L"Chaine"); - prog.sidChaine = (WORD)pNode->getInt(L"ServiceID"); + prog.apres = (ApresEnregistrement)pNode->getInt(L"After"); + prog.methode = (MethodeEnregistrement)pNode->getInt(L"Methode"); + prog.audio = (AudioMode)pNode->getInt(L"Audio"); // Ajustement de compatibilité permettant de récupérer correctement les programmations // faites avec les anciennes versions qui ne sauvegardaient pas le SID. - if (prog.sidChaine==0) { - int ixChaine = trouve_chaine_par_no(prog.numeroChaine); + if (prog.sidChaine==0) + prog.sidChaine = indexToSID(trouve_chaine_par_no(prog.numeroChaine)); - if (ixChaine_ok(ixChaine)) - prog.sidChaine = Canaux[ixChaine].SID; - } - // Récupère les heures/dates de début d'enregistrement CXMLNode *enregistrements = NULL; pNode->getChildrenByName(L"Début", &enregistrements); @@ -372,28 +388,34 @@ xmlLoadSystemTime(pNode, prog.fin, Xstn_E); } - prog.methode = (MethodeEnregistrement)pNode->getInt(L"Methode"); - prog.audio = (AudioMode)pNode->getInt(L"Audio"); - prog.after = (ApresEnregistrement)pNode->getInt(L"After"); prog.tache = (PlanificateurTache)pNode->getInt(L"Tache"); + prog.repetition = 0; CXMLNode *repetitions = NULL; pNode->getChildrenByName(L"Repetitions", &repetitions); // Au cas où on passe d'une ancienne version ne supportant pas les répétitions if (repetitions) { - for (int j=0; j<7; j++) - prog.repetition[j] = repetitions->getInt(Tbl_DoW[j])!=0; + for (int j=0; j<7; j++) { + if (repetitions->getInt(Tbl_DoW[j])!=0) + prog.repetition |= 1<getInt(L"Etat"); // 'epr_actif' par défaut (compatibilité) + Programmes.push_back(prog); delete pNode; pNode = NULL; } + // Tri de la liste des programmations (en cas de chargement + // d'ancienne version, ou bien de modification extérieure) : + std::sort(Programmes.begin(), Programmes.end()); + programmation_modifiee = false; return 0; } @@ -415,6 +437,9 @@ section); } +/** + * Sauvegarde des programmations d'enregistrements dans un fichier. + **/ void sauve_programmes(void) { int nbProgrammes = (int)Programmes.size(); @@ -432,13 +457,18 @@ for (int i=0; i\n" "\t\t%s\n" + "\t\t%i\n" "\t\t%i\n" "\t\t%i\n", - programme.nom, programme.numeroChaine, programme.sidChaine); + programme.nom, programme.nomAuto, programme.numeroChaine, programme.sidChaine); xmlSaveSystemTime(fop, "Début", programme.debut); xmlSaveSystemTime(fop, "Fin", programme.fin); @@ -448,13 +478,14 @@ "\t\t\n" "\t\t%i\n" "\t\t%i\n" + "\t\t%i\n" "\t\t\n", - programme.methode, programme.audio, programme.after, programme.tache); + programme.methode, programme.audio, programme.apres, programme.tache, etat); for (int j=0; j<7; j++) { LPCWSTR dow = Tbl_DoW[j]; - fprintf(fop, "\t\t\t<%S>%i\n", dow, programme.repetition[j], dow); + fprintf(fop, "\t\t\t<%S>%i\n", dow, (programme.repetition & (1<\n"); fclose(fop); + + programmation_modifiee = false; } -AssocElement aChStateTable[] = +/** + * Table d'associations pour nommer les états possible des chaînes. + **/ +const AssocElement aChStateTable[] = { {L"Inactive", ec_inactive}, {L"Active", ec_active}, @@ -476,6 +512,12 @@ }; /** + * Variable à mettre à 'true' toutes les fois que quelque chose a été modifié + * dans les enregistrements programmés, nécessitant une sauvegarde. + **/ +bool programmation_modifiee = false; + +/** * Chargement du fichier des chaînes "chaines.xml". * Retourne 0 en cas de succès, 1 si erreur. **/ @@ -710,7 +752,7 @@ save_config_str(ini_filtre_ac3, filtreAc3); save_config_str(ini_nom_ville, nomVille); save_config_bool(ini_strict, exact_match==TRUE); - save_config_int(ini_chaine_courante, ixChaine_ok(ixChaineCourante) ? Canaux[ixChaineCourante].SID : 0); + save_config_int(ini_chaine_courante, indexToSID(ixChaineCourante)); save_config_str(ini_rep_video, video_dir); save_config_str(ini_rep_screen, screenshots_dir); save_config_assoc(ini_priorite, aPrioTable, get_priority()); @@ -719,6 +761,7 @@ save_config_bool(ini_ac3_defaut, use_ac3); save_config_bool(ini_suspend_minimized, suspend_minimized); save_config_bool(ini_minimize_system_tray, minimize_system_tray); + save_config_bool(ini_allow_stream_record, allow_stream_record); save_config_bool(ini_use_all_width, use_all_width); save_config_bool(ini_etirer_video, etirer_video); save_config_bool(ini_use_vmr_deinterlace, use_vmr_deinterlace); @@ -731,9 +774,9 @@ save_config_int(ini_volume, (volumeCourant + 10000) / 100); } -DWORD load_config_str(LPCWSTR lpKeyName, LPWSTR lpValue, DWORD nSize, LPCWSTR lpDefault=L"") +DWORD load_config_str(LPCWSTR lpKeyName, LPWSTR lpValue, size_t bufSize, LPCWSTR lpDefault=L"") { - return GetPrivateProfileString(ini_config, lpKeyName, lpDefault, lpValue, nSize, confPathName); + return GetPrivateProfileString(ini_config, lpKeyName, lpDefault, lpValue, bufSize, confPathName); } UINT load_config_int(LPCWSTR lpKeyName, INT nDefault) @@ -769,16 +812,19 @@ } } -DWORD load_config_path(LPCWSTR lpKeyName, LPWSTR lpValue, DWORD nSize, int nDefaultFolder) +DWORD load_config_path(LPCWSTR lpKeyName, LPWSTR lpValue, size_t bufSize, int nDefaultFolder) { wchar_t default_dir[MAX_PATH]; SHGetFolderPath(NULL, nDefaultFolder, NULL, SHGFP_TYPE_CURRENT, default_dir); if (default_dir[0] == 0) SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, SHGFP_TYPE_CURRENT, default_dir); - return load_config_str(lpKeyName, lpValue, nSize, default_dir); + return load_config_str(lpKeyName, lpValue, bufSize, default_dir); } +/** + * Lecture de la configuration depuis un fichier. + **/ void load_config(void) { wchar_t chemin_config[MAX_PATH]; @@ -827,6 +873,7 @@ use_ac3 = load_config_bool(ini_ac3_defaut); suspend_minimized = load_config_bool(ini_suspend_minimized); minimize_system_tray = load_config_bool(ini_minimize_system_tray); + allow_stream_record = load_config_bool(ini_allow_stream_record); use_vmr_deinterlace = load_config_bool(ini_use_vmr_deinterlace); configState = (EtatsFenetre)load_config_assoc(ini_etat_fenetre, aWStateTable, etf_normal); @@ -842,6 +889,9 @@ volumeCourant = load_config_int(ini_volume, 100) * 100 - 10000; } +/** + * Chargement de la combo box 'hItm' à partir du fichier des noms de villes. + **/ void remplit_villes(HWND hItm) { wchar_t strings[8192]; @@ -871,7 +921,9 @@ } } -// vérifier que tuner TNT est valide +/** + * Vérification de la validité de la configuration du tuner TNT. + **/ bool check_config(void) { pNetworkTuner = get_tuner(KSCATEGORY_BDA_NETWORK_TUNER, nom_tuner); Modified: trunk/ini.h =================================================================== --- trunk/ini.h 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/ini.h 2007-12-14 16:03:59 UTC (rev 106) @@ -113,7 +113,7 @@ wchar_t nom[MAX_PATH]; // // Constructeur : - NomFichierAvecDate(LPCWSTR dir, LPCSTR prefixe, LPCSTR extension); + NomFichierAvecDate(LPCWSTR dir, LPCSTR prefixe1, LPCSTR prefixe2, LPCSTR extension); // // Création : HANDLE Create() const; @@ -147,4 +147,15 @@ /** * Table d'associations pour nommer les états possible des chaînes. **/ -extern AssocElement aChStateTable[]; +extern const AssocElement aChStateTable[]; + +/** + * Table des jours de la semaine pour sauvegarde et affichage. + **/ +extern LPCWSTR Tbl_DoW[7]; + +/** + * Variable à mettre à 'true' toutes les fois que quelque chose a été modifié + * dans les enregistrements programmés, nécessitant une sauvegarde. + **/ +extern bool programmation_modifiee; Modified: trunk/main.cpp =================================================================== --- trunk/main.cpp 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/main.cpp 2007-12-14 16:03:59 UTC (rev 106) @@ -115,6 +115,7 @@ bool cmd_minimize = false; // TRUE si "-minimize" en option ligne de commande bool suspend_minimized = false; bool minimize_system_tray = false; +bool allow_stream_record = false; bool is_vista = false; OSVERSIONINFOEX windows_version; @@ -400,20 +401,95 @@ } } +/** + * Trouve la position d'un item de menu référencé par son identifiant. + * Retourne -1 si pas trouvé. + **/ +int FindMenuItemPosition(HMENU hMenu, UINT uID) +{ + int nbItems = GetMenuItemCount(hMenu); + + for (int i=0; i pour se souvenir s'il y a des enregistrements en cours + for (int i=0; i=0) update_record_menus(hMainWnd); break; -#endif // #if USE_RECORD_STREAM case IDM_STOP_RECORD: grab = getGrabberChaine(ixChaineCourante); if (grab>=0) { - stop_record(grab); + enregistrements_actuels[grab].stop(epr_interrompu); update_record_menus(hMainWnd); } break; @@ -1958,6 +2036,11 @@ case IDM_DELAYED_STOP: if (recording()!=0) delayed_stop_dialog(); + else { + // (si on arrive ici, c'est qu'une mise à jour des menus + // a manqué quelque part et qu'il y a donc un bug) + update_record_menus(hMainWnd); + } break; case IDM_FULLSCREEN: { @@ -2069,14 +2152,15 @@ // cas changement piste switch_audio(ixSonCourant, wID-IDM_PISTES_BASE); update_menus(hMainWnd, 1, update_pistes_menu); + } else if (wID>=IDM_STOP_RECORD_BASE && wID=0) + if (do_record_events()) update_record_menus(hMainWnd); // Redemande le calcul du prochain enregistrement set_timer_record(); break; - - default: - grab = (int)(wTimerID-TIMER_RECORD_END_BASE); - if (grab >= 0 && grab < NB_MAX_ENREG) { - // Gestion du timer de fin d'enregistrement : - do_record_stop(grab); - update_record_menus(hMainWnd); - } } } @@ -2342,6 +2418,12 @@ do_timer(wParam); break; + case WM_TIMECHANGE: + // Un changement est intervenu dans l'heure système ou la programmation des + // fins d'enregistrements. + set_timer_record(); // Recalculer le timer des enregistrements + break; + case WM_LBUTTONDBLCLK: KillTimer(hWnd, TIMER_BORDER); PostMessage(hWnd, WM_COMMAND, MAKELONG(IDM_FULLSCREEN, 1), 0); @@ -2756,11 +2838,6 @@ **/ switch_etat_fenetre(configState); -#if USE_RECORD_STREAM - InsertMenuA(GetSubMenu(hMainMenu, 5), 3, MF_BYPOSITION, IDM_RECORD_STREAM, - "Enregistrer le &multiplex\tCtrl+M"); -#endif // #if USE_RECORD_STREAM - update_coords(); // Charge le tableau des raccourcis claviers Modified: trunk/main.h =================================================================== --- trunk/main.h 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/main.h 2007-12-14 16:03:59 UTC (rev 106) @@ -54,6 +54,7 @@ extern bool suspend_minimized; extern bool minimize_system_tray; +extern bool allow_stream_record; extern bool use_msn; extern bool on_top; extern bool use_ac3; @@ -83,10 +84,9 @@ #define TIMER_BORDER 124 #define TIMER_OSD 125 #define TIMER_CURSOR 126 -#define TIMER_RECORD_START 127 -#define TIMER_RECORD_END_BASE 130 -#define TIMER_RECORD_END_FIN (TIMER_RECORD_END_BASE + NB_MAX_ENREG + 1) -#define TIMER_UPDATE (TIMER_RECORD_END_FIN + 1) +#define TIMER_DELAYED_RECORD 127 +#define TIMER_DELAYED_STOP 128 +#define TIMER_UPDATE 129 DWORD get_priority(); Modified: trunk/record.cpp =================================================================== --- trunk/record.cpp 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/record.cpp 2007-12-14 16:03:59 UTC (rev 106) @@ -34,27 +34,76 @@ Enregistrement enregistrements_actuels[NB_MAX_ENREG]; -void Enregistrement::start(int ixChaine, ISampleGrabberCB * pCallback, ApresEnregistrement apr) +void Enregistrement::start(ISampleGrabberCB * pCallback, int ixChaine, Programme * pProg) { - stop(); // précaution - if (pSample && pCallback && ixChaine!=-1) { + WORD sidCh = indexToSID(ixChaine); + + stop(epr_interrompu); // précaution + if (pSample && pCallback && sidCh!=0) { + if (pProg) { + memcpy(this, pProg, sizeof(RecordInfo)); + pProg->etat = epr_encours; + programmation_modifiee = true; + } pSample->SetCallback(pCallback, 0); - indexChaine = ixChaine; - apres = apr; + sidChaine = sidCh; } } -ApresEnregistrement Enregistrement::stop() +// Prise en compte d'une programmation affectant une chaîne déjà en cours d'enregistrement +bool Enregistrement::modify_prog(Programme * pProg) { - ApresEnregistrement apr = apres; + if (pProg && DiffTime(pProg->fin, fin)>0) { + programmation_modifiee = true; + if (pProg->methode!=methode || pProg->audio!=audio) { + pProg->etat = epr_erreur; // état de la nouvelle programmation utilisée + return false; + } else { + int ixProg = trouve_prog_par_nom(nom); - if (pSample && indexChaine!=-1) - pSample->SetCallback(NULL, 0); - indexChaine = -1; - apres = apr_rien; - return apr; + if (ixProg>=0) + Programmes[ixProg].etat = epr_supplante; // état de l'ancienne programmation utilisée + + fin = pProg->fin; + apres = pProg->apres; + pProg->etat = epr_encours; // état de la nouvelle programmation utilisée + strcpy_s(nom, _countof(nom), pProg->nom); + } + } + return true; } +/** + * Arrêt de l'enregistrement. Retourne l'action à effectuer ensuite, + * ou bien 'apr_null' si l'enregistrement n'était pas en cours. + **/ +ApresEnregistrement Enregistrement::stop(EtatProgramme raison) +{ + ApresEnregistrement after = apr_null; + + if (recording()) { + after = apres; + + if (pSample && sidChaine>0) { + if (raison!=epr_null) { + int ixProg = trouve_prog_par_nom(nom); + + if (ixProg>=0) { + Programmes[ixProg].etat = raison; + programmation_modifiee = true; + } + } + + pSample->SetCallback(NULL, 0); + } + + nom[0] = 0; + sidChaine = 0; + apres = apr_null; + } + return after; +} + void Enregistrement::query(IBaseFilter * pGrabber) { if (pSample==NULL) @@ -67,9 +116,38 @@ } /** + * Obtenir le nom de la chaîne enregistrée (retourne un "*" s'il s'agit du multiplex entier) + **/ +LPCSTR Enregistrement::channelName() const +{ + if (sidChaine==STREAM_PSEUDO_SID) + return "*"; + + int ixChaine = trouve_chaine_par_sid(sidChaine); + + if (ixChaine_ok(ixChaine)) + return Canaux[ixChaine].nom; + return "??"; +} + +/** + * Calcul du temps restant avant fin enregistrement en millisecondes. + * L'heure de référence (en principe l'heure système) doit être passée en paramètre. + **/ +long Enregistrement::temps_restant(const SYSTEMTIME & localtime) const +{ + if (!recording()) + return DUREE_INFINIE; + + long reste = DiffTime(fin, localtime); + + return max(reste, 0); +} + +/** * Détermine si on enregistre actuellement * - * Renvoie 1 si on enregistre actuellement, 0 sinon + * Renvoie le SID de la première chaîne enregistrée trouvée si on enregistre actuellement, 0 sinon **/ WORD recording() { @@ -77,34 +155,18 @@ const Enregistrement & enreg = enregistrements_actuels[i]; if (enreg.recording()) - return 1; + return enreg.sidChaine; } return 0; } /** - * Arrêt de l'enregistrement d'index 'grab'. Retourne le code correspondant - * à l'action qui avait été programmée à l'issue de cet enregistrement. - **/ -ApresEnregistrement stop_record(int grab) -{ - ApresEnregistrement after = apr_rien; - Enregistrement & enreg = enregistrements_actuels[grab]; - - if (enreg.recording()) { - KillTimer(hMainWnd, TIMER_RECORD_END_BASE+grab); - after = enreg.stop(); - } - return after; -} - -/** * Arrête tous les enregistrements **/ void stop_all_records() { for (int i=0; i<_countof(enregistrements_actuels); i++) - stop_record(i); + enregistrements_actuels[i].stop(epr_null); // epr_null -> ne pas mémoriser cet arrêt } /** @@ -114,9 +176,13 @@ **/ int getGrabberChaine(int ixChaine) { - for (int i=0; i<_countof(enregistrements_actuels); i++) { - if (enregistrements_actuels[i].isChannel(ixChaine)) - return i; + WORD sidChaine = indexToSID(ixChaine); + + if (sidChaine>0) { + for (int i=0; i<_countof(enregistrements_actuels); i++) { + if (enregistrements_actuels[i].isChannel(sidChaine)) + return i; + } } return -1; } @@ -131,49 +197,71 @@ if (!enregistrements_actuels[i].recording()) return i; } + myprintf(L"Plus aucun descripteur d'enregistrement disponible\n"); return -1; } -// Démarrage d'un enregistrement en mode TS, pour la chaîne dont l'index est égal à ixChaine -// Le tuner est supposé être déjà syntonisé sur la bonne fréquence -// Toutes les pistes audio sont enregistrées -// Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. -// TODO : retirer la restriction sur la syntonisation -// TODO : ajouter le paramètre 'ixSon' pour choisir entre l'enregistrement d'une piste audio donnée, -// ou bien toutes les pistes audio si -1 -int start_record_ts(int ixChaine) +/** + * Obtenir le pointeur sur le nom d'une programmation si celle-ci a été explicitement + * nommée, et NULL dans le cas contraire : + **/ +static LPSTR getProgName(Programme * pProg) { + if (pProg!=NULL && !pProg->nomAuto && *pProg->nom!=0) + return pProg->nom; + return NULL; +} + +/** + * Démarrage d'un enregistrement en mode TS, pour la chaîne dont l'index est égal à ixChaine. + * Le tuner est supposé être déjà syntonisé sur la bonne fréquence. + * Toutes les pistes audio sont enregistrées. + * 'pProg' est un pointeur facultatif sur la programmation horaire ayant servi à démarrer + * l'enregistrement : s'il n'est pas NULL, des paramètres additionnels y sont récupérés. + * Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. + * TODO : retirer la restriction sur la syntonisation + * TODO : ajouter le paramètre 'ixSon' pour choisir entre l'enregistrement d'une piste audio donnée, + * ou bien toutes les pistes audio si -1 + **/ +int start_record_ts(int ixChaine, Programme * pProg) +{ if (!ixChaine_ok(ixChaine)) return -1; int grab = getFreeGrabber(); if (grab>=0) { const Chaine & canal = Canaux[ixChaine]; - NomFichierAvecDate nom_fichier(video_dir, canal.nom, "ts"); + NomFichierAvecDate nom_fichier(video_dir, getProgName(pProg), canal.nom, "ts"); int video = canal.video_pid ? canal.video_pid : canal.mpeg4_pid; + ISampleGrabberCB * pCallback = NULL; + HANDLE hFile = nom_fichier.Create(); - ISampleGrabberCB * pCallback = - new CSampleGrabber( - nom_fichier.Create(), - canal.pmt_pid, - video, - canal.son, - canal.nb_son, - canal.autre, - canal.nb_autre); + if (hFile != INVALID_HANDLE_VALUE) { + pCallback = new CSampleGrabber(hFile, canal.pmt_pid, video, + canal.son, canal.nb_son, canal.autre, canal.nb_autre); - enregistrements_actuels[grab].start(ixChaine, pCallback); + if (pCallback!=NULL) + enregistrements_actuels[grab].start(pCallback, ixChaine, pProg); + } else + myprintf(L"Erreur création '%s' code %u\n", nom_fichier.nom, GetLastError()); + + if (pCallback==NULL) + return -1; } return grab; } -// Démarrage d'un enregistrement en mode PS, pour la chaîne dont l'index -// est égal à 'ixChaine', avec 'ixSon' comme piste son -// Le tuner est supposé être déjà syntonisé sur la bonne fréquence -// Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. -// TODO : retirer la restriction sur la syntonisation -// TODO : faire en sorte que toutes les pistes son soient enregistrées si 'ixSon'=-1 -int start_record_ps(int ixChaine, int ixSon) +/** + * Démarrage d'un enregistrement en mode PS, pour la chaîne dont l'index. + * est égal à 'ixChaine', avec 'ixSon' comme piste son. + * Le tuner est supposé être déjà syntonisé sur la bonne fréquence. + * 'pProg' est un pointeur facultatif sur la programmation horaire ayant servi à démarrer + * l'enregistrement : s'il n'est pas NULL, des paramètres additionnels y sont récupérés. + * Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. + * TODO : retirer la restriction sur la syntonisation + * TODO : faire en sorte que toutes les pistes son soient enregistrées si 'ixSon'=-1 + **/ +int start_record_ps(int ixChaine, int ixSon, Programme * pProg) { if (!ixChaine_ok(ixChaine)) return -1; @@ -181,36 +269,51 @@ int grab = getFreeGrabber(); if (grab>=0) { const Chaine & canal = Canaux[ixChaine]; - NomFichierAvecDate nom_fichier(video_dir, canal.nom, "mpg"); + NomFichierAvecDate nom_fichier(video_dir, getProgName(pProg), canal.nom, "mpg"); int video = canal.video_pid ? canal.video_pid : canal.mpeg4_pid; int son = canal.son[ixSon].pid; + ISampleGrabberCB * pCallback = NULL; + HANDLE hFile = nom_fichier.Create(); - ISampleGrabberCB * pCallback = - new CSampleGrabber( - nom_fichier.Create(), - video, - son); + if (hFile != INVALID_HANDLE_VALUE) { + pCallback = new CSampleGrabber(hFile, video, son); - enregistrements_actuels[grab].start(ixChaine, pCallback); + if (pCallback!=NULL) + enregistrements_actuels[grab].start(pCallback, ixChaine, pProg); + } else + myprintf(L"Erreur création '%s' code %u\n", nom_fichier.nom, GetLastError()); + + if (pCallback==NULL) + return -1; } return grab; } -#if USE_RECORD_STREAM -// Démarrage d'un enregistrement global du multiplex. -// Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. -// TODO : ajouter un paramètre (par ex. ixChaine) pour que soit préalablement syntonisé le multiplex -// correspondant à la chaîne désignée. -int start_record_stream() +/** + * Démarrage d'un enregistrement global du multiplex. + * Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. + * 'pProg' est un pointeur facultatif sur la programmation horaire ayant servi à démarrer + * l'enregistrement : s'il n'est pas NULL, des paramètres additionnels y sont récupérés. + * TODO : ajouter un paramètre (par ex. ixChaine) pour que soit préalablement syntonisé le multiplex + * correspondant à la chaîne désignée. + **/ +int start_record_stream(Programme * pProg) { int grab = getFreeGrabber(); if (grab>=0) { - NomFichierAvecDate nom_fichier(video_dir, "Transport Stream", "ts"); + NomFichierAvecDate nom_fichier(video_dir, getProgName(pProg), "Transport Stream", "ts"); + ISampleGrabberCB * pCallback = NULL; + HANDLE hFile = nom_fichier.Create(); - ISampleGrabberCB * pCallback = new CSampleGrabber(nom_fichier.Create()); + if (hFile != INVALID_HANDLE_VALUE) { + pCallback = new CSampleGrabber(hFile); - enregistrements_actuels[grab].start(STREAM_PSEUDO_INDEX, pCallback); + enregistrements_actuels[grab].start(pCallback, STREAM_PSEUDO_INDEX, pProg); + } else + myprintf(L"Erreur création '%s' code %u\n", nom_fichier.nom, GetLastError()); + + if (pCallback==NULL) + return -1; } return grab; } -#endif // #if USE_RECORD_STREAM Modified: trunk/record.h =================================================================== --- trunk/record.h 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/record.h 2007-12-14 16:03:59 UTC (rev 106) @@ -34,22 +34,45 @@ // devrait pas poser de problèmes) #define DUREE_INFINIE long((unsigned long)-1 >> 1) // = 0x7fffffff, si 'long' est sur 32 bits -class Enregistrement { - int indexChaine; // Numéro de la chaine enregistrée - ApresEnregistrement apres; // Ce que doit faire Pouchin TV Mod une fois l'enregistrement terminé - ISampleGrabber * pSample; +// Durée maximale qui sépare deux appels au timer de début d'enregistrement. +// Si le temps avant l'enregistrement suivant est plus long que ce délai, +// le timer est programmé sur cette valeur, et est donc de fait recalculé +// à intervalles identiques à cette valeur jusqu'à ce que la durée restante +// soit inférieure. +#if _DEBUG + #define DUREE_RECALCUL_TIMERS 10 // Valeur en secondes +#else + #define DUREE_RECALCUL_TIMERS (10*60) // Valeur en secondes +#endif + +class Enregistrement : public RecordInfo { public: + ISampleGrabber * pSample; // // Mini-constructeur : - Enregistrement() : indexChaine(-1), apres(apr_rien), pSample(NULL) {} + Enregistrement() : pSample(NULL) {} // - bool recording() const {return indexChaine >=0 ;} - bool isChannel(int ixChaine) const {return indexChaine == ixChaine;} - void start(int ixChaine, ISampleGrabberCB * pCallback, ApresEnregistrement apr = apr_rien); + bool recording() const {return sidChaine >0;} + bool isChannel(WORD sidCh) const {return sidChaine == sidCh;} + + void start(ISampleGrabberCB * pCallback, int ixChaine, Programme * pProg); + + // Prise en compte d'une programmation affectant une chaîne déjà en cours d'enregistrement + bool modify_prog(Programme * pProg); + void query(IBaseFilter * pGrabber); void free(); - void setAfter(ApresEnregistrement apr) {apres = apr;} - ApresEnregistrement stop(); + + // Calcul du temps restant avant fin enregistrement en millisecondes. + // L'heure de référence (en principe l'heure système) doit être passée en paramètre. + long temps_restant(const SYSTEMTIME & localtime) const; + + // Arrêt de l'enregistrement. Retourne l'action à effectuer ensuite, + // ou bien 'apr_null' si l'enregistrement n'était pas en cours. + ApresEnregistrement stop(EtatProgramme raison); + + // Obtenir le nom de la chaîne enregistrée (retourne un "*" s'il s'agit du multiplex entier) : + LPCSTR channelName() const; }; extern Enregistrement enregistrements_actuels[NB_MAX_ENREG]; @@ -57,18 +80,11 @@ /** * Détermine si on enregistre actuellement * - * Renvoie 1 si on enregistre actuellement, 0 sinon + * Renvoie le SID de la première chaîne enregistrée trouvée si on enregistre actuellement, 0 sinon **/ WORD recording(); - /** - * Arrêt de l'enregistrement d'index 'grab'. Retourne le code correspondant - * à l'action qui avait été programmée à l'issue de cet enregistrement. - **/ -ApresEnregistrement stop_record(int grab); - -/** * Arrêt de tous les enregistrements en cours **/ void stop_all_records(); @@ -90,32 +106,36 @@ * Démarrage d'un enregistrement en mode TS, pour la chaîne dont l'index est égal à ixChaine. * Le tuner est supposé être déjà syntonisé sur la bonne fréquence. * Toutes les pistes audio sont enregistrées. + * 'pProg' est un pointeur facultatif sur la programmation horaire ayant servi à démarrer + * l'enregistrement : s'il n'est pas NULL, des paramètres additionnels y sont récupérés. * Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. * TODO : retirer la restriction sur la syntonisation * TODO : ajouter le paramètre 'ixSon' pour choisir entre l'enregistrement d'une piste audio donnée, * ou bien toutes les pistes audio si -1 **/ -int start_record_ts(int ixChaine); +int start_record_ts(int ixChaine, Programme * pProg=NULL); /** * Démarrage d'un enregistrement en mode PS, pour la chaîne dont l'index. * est égal à 'ixChaine', avec 'ixSon' comme piste son. * Le tuner est supposé être déjà syntonisé sur la bonne fréquence. + * 'pProg' est un pointeur facultatif sur la programmation horaire ayant servi à démarrer + * l'enregistrement : s'il n'est pas NULL, des paramètres additionnels y sont récupérés. * Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. * TODO : retirer la restriction sur la syntonisation * TODO : faire en sorte que toutes les pistes son soient enregistrées si 'ixSon'=-1 **/ -int start_record_ps(int ixChaine, int ixSon); +int start_record_ps(int ixChaine, int ixSon, Programme * pProg=NULL); -#if USE_RECORD_STREAM #define STREAM_PSEUDO_SID 0xffff // Pseudo SID pour l'enregistrement du multiplex #define STREAM_PSEUDO_INDEX -2 // Pseudo index de chaîne pour l'enregistrement du multiplex /** * Démarrage d'un enregistrement global du multiplex. * Retourne l'index dans la table des enregistrements, ou bien -1 si aucun enregistrement n'a pu démarrer. + * 'pProg' est un pointeur facultatif sur la programmation horaire ayant servi à démarrer + * l'enregistrement : s'il n'est pas NULL, des paramètres additionnels y sont récupérés. * TODO : ajouter un paramètre (par ex. ixChaine) pour que soit préalablement syntonisé le multiplex * correspondant à la chaîne désignée. **/ -int start_record_stream(); -#endif \ No newline at end of file +int start_record_stream(Programme * pProg=NULL); Modified: trunk/recprog.cpp =================================================================== --- trunk/recprog.cpp 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/recprog.cpp 2007-12-14 16:03:59 UTC (rev 106) @@ -34,10 +34,8 @@ // #include #include +#include // pour 'sort' -// Type liste de tâches à supprimer : -typedef std::vector ListeTaches; - // Délai de grâce pour l'arrêt après enregistrement : si, à l'arrêt d'un enregistrement, un // autre enregistrement est programmé dans un délai plus court que le délai spécifié, alors // les options d'arrêt du programme ou d'extinction de l'ordinateur sont ignorées : @@ -48,12 +46,21 @@ // terminaison propre de l'enregistrement : #define DUREE_QUIT 2000 // Valeur en millisecondes +// Marge pour le calcul des recouvrements de programmations : +// Deux programmations doivent être séparées par au moins ce temps +// (en millisecondes) pourqu'il soit considéré qu'elles ne se superposent pas +#define DUREE_MARGE_RECOUVREMENT 2500 // Valeur en millisecondes + std::vector Programmes; -int next_record = -1; static HWND hRecordDlg = NULL; static wchar_t mdp[256] = L""; +// Variables utilisées pendant les modifications des contrôles date et heure pour tester la +// différence entre le début et la fin ainsi que la différence avec la valeur avant changement : +static SYSTEMTIME dtc_debut = {0}; +static SYSTEMTIME dtc_fin = {0}; + /** * On passe un SID de chaine en paramètre, * et on retourne la fréquence du multiplex correspondant @@ -89,13 +96,173 @@ } /** + * Fonction qui ajoute 'add' millisecondes au temps 'time. + **/ +void AddTime(SYSTEMTIME & time, long add) +{ + INT64 Utime; + + SystemTimeToFileTime(&time, LPFILETIME(&Utime)); + Utime += INT64(add)*10000; + FileTimeToSystemTime(LPFILETIME(&Utime), &time); +} + +static INT_PTR CALLBACK PaswdDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { + switch (uMsg) { + case WM_INITDIALOG: + return TRUE; + + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDOK: + char buffer[256]; + buffer[0] = 127; + SendDlgItemMessage(hwndDlg, IDC_PASWD, EM_GETLINE, 0, (LPARAM)buffer); + for(int i = 1; buffer[2 * (i - 1)] != 0; i++) + buffer[i] = buffer[2 * i]; + MultiByteToWideChar(CP_ACP, 0, buffer, -1, mdp, _countof(mdp)); + EndDialog(hwndDlg, 0); + return TRUE; + case IDCANCEL: + EndDialog(hwndDlg, 1); + return TRUE; + } + return FALSE; + + default: + return FALSE; + } +} + +static bool taskLogon(HWND hDlg, LPCWSTR username) +{ + // Vérifie le login, et demande le mot de passe si besoin est + bool identifie = false; + + HANDLE session; + do { + identifie = LogonUser(username, NULL, mdp, + LOGON32_LOGON_NETWORK, LOGON32_PROVIDER_DEFAULT, &session)==TRUE; + if (identifie) + break; + + // Saisie du mot de passe, et reboucler si pas d'annulation : + mdp[0] = 0; // (vider la saisie précédente) + } while (DialogBox(hAppInstance, MAKEINTRESOURCE(IDD_PASWD), hDlg, PaswdDialogProc)==0); + + CloseHandle(session); + + return identifie; +} + +// Supprimer une tâche depuis le programmateur de tâches : +static int supprime_une_tache(LPCSTR nom) +{ + WCHAR pwszTaskName[256]; + ITaskScheduler * pITS; + + NomProtege nom_tache(nom, '_'); + + nom_tache.ToWide(pwszTaskName, _countof(pwszTaskName)); + + HRESULT hr = CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, (void**)&pITS); + if (FAILED(hr)) { + myprintf(L"Erreur init task scheduler\n"); + return 1; + } + + // Supprime la tâche + pITS->Delete(pwszTaskName); + + pITS->Release(); + return 0; +} + +// Constructeur : +Programme::Programme() +{ + apres = apr_rien; + memset(&debut, 0, LPBYTE(this)+sizeof(*this)-LPBYTE(&debut)); + etat = epr_inactif; +} + + + +// Retourne 'true' si intersection temporelle avec la programmation 'p2' +bool Programme::overlap(const Programme & p2) const +{ + // avec 1000 millisecondes de marge + return + DiffTime(fin, p2.debut) >= DUREE_MARGE_RECOUVREMENT && + DiffTime(p2.fin, debut) >= DUREE_MARGE_RECOUVREMENT; +} + +// Utilisé par la fonction std::sort : +bool Programme::operator < (const Programme & p2) const +{ + return DiffTime(debut, p2.debut)<0; +} + +/** + * Calcul du temps restant avant début enregistrement en millisecondes. + * L'heure de référence (en principe l'heure système) doit être passée en paramètre. + **/ +long Programme::temps_restant(const SYSTEMTIME & localtime) const +{ + if (!active() || DiffTime(localtime, fin)>=0) + return DUREE_INFINIE; // La programmation est inactive ou l'heure de fin est dépassée + + long reste = DiffTime(debut, localtime); + + return max(reste, 0); +} + +/** + * Ajustement des données si répétitions programmées et horaire de fin dépassé. + * L'heure de référence (en principe l'heure système) doit être passée en paramètre. + * Retourne 'true' si un ajustement a eu lieu + **/ +bool Programme::ajustementDesRepetitions(const SYSTEMTIME & time) +{ + bool res = false; + + if (repetition!=0 && !active()) { + while (DiffTime(fin, time) <= 0) { + int nbJours; + + // Trouver le nombre de jours avant la répétition suivante : + for (nbJours=1; (repetition & (1<<((debut.wDayOfWeek + nbJours) % 7)))==0; nbJours++); + + long to_add = nbJours * 24 * 60 * 60 * 1000; // Conversion en millisecondes + + AddTime(debut, to_add); + AddTime(fin, to_add); + + // TODO : il faudrait vérifier d'une façon ou d'une autre qu'une transition + // heure d'été <-> heure d'hiver intempestive ne risque pas d'introduire un + // décalage d'une heure dans la programmation à cet endroit. + + etat = epr_actif; // réactivation + programmation_modifiee = true; + res = true; + + // Note : on revérifie en bouclant sur le test de différence de temps pour parer au cas + // où celui-ci serait effectué alors que le programme n'a pas été exécuté pendant un délai + // excédant plusieurs répétitions consécutives. + } + } + return res; +} + +/** * Vérification de la validité de la programmation 'prog' + * (mettre 'modif' à 'true' si on est en train de modifier une programmation) * Retourne true si ok, false si erreur. **/ -bool Programme::verifie() const +bool Programme::verifie(bool modif) const { int ixChaine = trouve_chaine_par_sid(sidChaine); - SYSTEMTIME time; + SYSTEMTIME localtime; wchar_t buffer[256]; if (ixChaine<0) { @@ -103,18 +270,30 @@ return false; } - GetLocalTime(&time); + GetLocalTime(&localtime); - if (DiffTime(debut, time) < 0) { - erreur(L"L'heure de commencement du programme est dépassée !"); + long duree = DiffTime(fin, debut); + + if (duree < 0) { + erreur(L"Le programme commence après avoir fini !"); return false; } - if (DiffTime(debut, fin) > 0) { - erreur(L"Le programme commence après avoir fini !"); + if (duree == 0) { + erreur(L"La durée de l'enregistrement est nulle !"); return false; } + if (duree > 12*60*60*1000) { + erreur(L"La durée de l'enregistrement dépasse 12 heures !"); + return false; + } + + if (DiffTime(fin, localtime) < 0) { + erreur(L"L'heure de fin du programme est dépassée !"); + return false; + } + if (nom[0] == 0) { erreur(L"Veuillez donner un nom à ce programme"); return false; @@ -126,49 +305,68 @@ for(int i = 0; i < nbProgrammes; i++) { const Programme & prog_verif = Programmes[i]; - if(strcmp(prog_verif.nom, nom) == 0) { - erreur(L"Ce nom est déjà utilisé"); - return false; + if (!modif) { + if (strcmp(prog_verif.nom, nom) == 0) { + erreur(L"Ce nom est déjà utilisé"); + return false; + } } - // S'il y a chevauchement des horaires, vérifie que le programme est sur le même multiplex : - if (DiffTime(prog_verif.debut, fin) <= 0 && - DiffTime(prog_verif.fin, debut) >= 0 && - getFrequence_par_sid(prog_verif.sidChaine) != canal.frequence - ) { - swprintf_s(buffer, _countof(buffer), - L"Chevauchement des horaires avec \"%S\" sur %S, qui ne partage pas le même multiplex !", - prog_verif.nom, canal.nom); - erreur(buffer); - return false; + + if (prog_verif.active() && overlap(prog_verif)) { + // Chevauchement des horaires : + + // Vérifier qu'on n'a pas programmé plusieurs fois la même chaîne au même moment : + if (!modif && sidChaine==prog_verif.sidChaine && isMultiplex()==prog_verif.isMultiplex()) { + swprintf_s(buffer, _countof(buffer), + L"Chevauchement des horaires avec \"%S\", qui concerne la même chaîne !", + prog_verif.nom); + erreur(buffer); + return false; + } + + // Vérifie que le programme chevauchant est sur le même multiplex : + if (getFrequence_par_sid(prog_verif.sidChaine) != canal.frequence) { + swprintf_s(buffer, _countof(buffer), + L"Chevauchement des horaires avec \"%S\" sur %S, qui ne partage pas le même multiplex !", + prog_verif.nom, canal.nom); + erreur(buffer); + return false; + } } } return true; // pas d'erreur } + +// Génération du nom de tâche programmée correspondant +void Programme::genereNomTache(LPWSTR pstr, size_t bufSize) const +{ + // Remplace les caracteres incorrects : + NomProtege nom_tache(nom, '_'); + + // On ajoute un préfixe : + wcscpy_s(pstr, bufSize, L"PTV-"); + pstr += 4; + bufSize -= 4; + + // On convertit le nom de la tâche en Unicode : + if (bufSize > 1) + nom_tache.ToWide(pstr, bufSize); +} + // Ajout de cette programmation aux tâches programmées (retourne 'false' si échec) bool Programme::ajouterTacheProgrammee(LPCWSTR username, LPCWSTR motdepasse) const { - WCHAR pwszTaskName[256]; - WORD piNewTrigger; - TASK_TRIGGER Trig; - ITaskScheduler * pITS; - ITask * pTask; - IPersistFile * pPersistFile; - ITaskTrigger * pTaskTrigger; + WCHAR taskName[_countof(nom)+4]; + ITaskScheduler * pITS; + SYSTEMTIME date = debut; - SYSTEMTIME date = debut; - UINT64 fdebut; - SystemTimeToFileTime(&date, (LPFILETIME)&fdebut); - fdebut = fdebut - (UINT64)600000000; - FileTimeToSystemTime((LPFILETIME)&fdebut, &date); + AddTime(date, -60*1000); // Soustraire 60 secondes - // Remplace les caracteres incorrects : - NomProtege nom_tache(nom, '_'); + // On génère le nom de la tâche : + genereNomTache(taskName, _countof(taskName)); - // On convertit le nom de la tâche en Unicode : - nom_tache.ToWide(pwszTaskName, _countof(pwszTaskName)); - // Accède au planificateur pour supprimer la tâche si elle existe HRESULT hr = CoCreateInstance(CLSID_CTaskScheduler, NULL, CLSCTX_INPROC_SERVER, IID_ITaskScheduler, (void**)&pITS); if (FAILED(hr)) { @@ -176,12 +374,19 @@ return false; } // Supprime la tâche - pITS->Delete(pwszTaskName); + hr = pITS->Delete(taskName); + // Crée la nouvelle tâche - hr = pITS->NewWorkItem(pwszTaskName, CLSID_CTask, IID_ITask, (IUnknown**)&pTask); + ITask * pTask; + hr = pITS->NewWorkItem(taskName, CLSID_CTask, IID_ITask, (IUnknown**)&pTask); pITS->Release(); + if (FAILED(hr)) { + myprintf(L"Erreur création tâche %s\n", taskName); + return false; + } + wchar_t path[MAX_PATH]; // Définition de la tâche @@ -190,68 +395,101 @@ pTask->SetParameters(L"-minimize"); GetCurrentDirectory(_countof(path), path); pTask->SetWorkingDirectory(path); - hr=pTask->SetAccountInformation(username, motdepasse); - pTask->SetFlags(TASK_FLAG_DELETE_WHEN_DONE | TASK_FLAG_SYSTEM_REQUIRED); - pTask->SetMaxRunTime(INFINITE); - pTask->CreateTrigger(&piNewTrigger, &pTaskTrigger); - ZeroMemory(&Trig, sizeof(TASK_TRIGGER)); - Trig.cbTriggerSize = sizeof(TASK_TRIGGER); - Trig.wBeginDay = date.wDay; - Trig.wBeginMonth = date.wMonth; - Trig.wBeginYear = date.wYear; - Trig.wStartHour = date.wHour; - Trig.wStartMinute = date.wMinute; + hr = pTask->SetAccountInformation(username, motdepasse); + if (SUCCEEDED(hr)) { + WORD piNewTrigger; + ITaskTrigger * pTaskTrigger; - bool repete = false; - for (int i=0; i<7; i++) - if (repetition[i]) { - repete = true; - break; - } + pTask->SetFlags(TASK_FLAG_DELETE_WHEN_DONE | TASK_FLAG_SYSTEM_REQUIRED); + pTask->SetMaxRunTime(INFINITE); + pTask->CreateTrigger(&piNewTrigger, &pTaskTrigger); - if (repete) { - // On a demandé à ce que la tâche soit répétée - Trig.TriggerType = TASK_TIME_TRIGGER_WEEKLY; + TASK_TRIGGER Trig = { + sizeof(Trig), // cbTriggerSize + 0, // Reserved1 + date.wYear, // wBeginYear + date.wMonth, // wBeginMonth + date.wDay, // wBeginDay + 0, // wEndYear + 0, // wEndMonth + 0, // wEndDay + date.wHour, // wStartHour + date.wMinute, // wStartMinute + 0, // rgFlags + TASK_TIME_TRIGGER_ONCE // TriggerType (Pas de répétition de la tâche, par défaut) + // le reste à zéro (implicite) + }; - WEEKLY jours; - jours.WeeksInterval = 1; // Répète toutes les semaines - jours.rgfDaysOfTheWeek = 0; + if (repetition!=0) { + // On a demandé à ce que la tâche soit répétée + Trig.TriggerType = TASK_TIME_TRIGGER_WEEKLY; - if (repetition[1]) - jours.rgfDaysOfTheWeek += TASK_MONDAY; - if (repetition[2]) - jours.rgfDaysOfTheWeek += TASK_TUESDAY; - if (repetition[3]) - jours.rgfDaysOfTheWeek += TASK_WEDNESDAY; - if (repetition[4]) - jours.rgfDaysOfTheWeek += TASK_THURSDAY; - if (repetition[5]) - jours.rgfDaysOfTheWeek += TASK_FRIDAY; - if (repetition[6]) - jours.rgfDaysOfTheWeek += TASK_SATURDAY; - if (repetition[0]) - jours.rgfDaysOfTheWeek += TASK_SUNDAY; + WEEKLY & jours = Trig.Type.Weekly; - Trig.Type.Weekly = jours; - } else { - // Pas de répétition de la tâche - Trig.TriggerType = TASK_TIME_TRIGGER_ONCE; - } + jours.WeeksInterval = 1; // Répète toutes les semaines + jours.rgfDaysOfTheWeek = 0; - pTaskTrigger->SetTrigger(&Trig); + static const BYTE repFlgs[7] = + {TASK_SUNDAY, TASK_MONDAY, TASK_TUESDAY, TASK_WEDNESDAY, + TASK_THURSDAY, TASK_FRIDAY, TASK_SATURDAY}; - pTask->QueryInterface(IID_IPersistFile, (void **)&pPersistFile); - pPersistFile->Save(NULL, TRUE); - pPersistFile->Release(); + for (int i=0; i<7; i++) { + if ((repetition & (1<Release(); + pTaskTrigger->SetTrigger(&Trig); + + IPersistFile * pPersistFile; + + hr = pTask->QueryInterface(IID_IPersistFile, (void **)&pPersistFile); + if (SUCCEEDED(hr)) { + pPersistFile->Save(NULL, TRUE); + pPersistFile->Release(); + } + + pTaskTrigger->Release(); + } pTask->Release(); return true; } /** + * Ajout de la programmation si nécessaire, incluant identification + * Retourne 'true' si une programmation a effectivement été réalisée + **/ +bool Programme::ajoutTacheAvecLogon(HWND hDlg) +{ + // Par précaution, détruire d'abord toute tâche antérieurement existante portant + // le nom demandé : + supprime_une_tache(nom); + + if (etat==epr_actif && tache == planif_ajout) { + wchar_t username[256]; + DWORD iLen = _countof(username); + + GetUserName(username, &iLen); // Obtenir le nom de l'utilisateur courant + + if (taskLogon(hDlg, username)) { + if (ajouterTacheProgrammee(username, mdp)) { + tache = planif_avec; + return true; + } + } else { + tache = planif_sans; + CheckDlgButton(hDlg, IDC_TACHE, BST_UNCHECKED); + MessageBox(hDlg, L"Vous n'avez pas saisi de mot de passe valide :\n" + L"Cet enregistrement va être programmé sans ajout au gestionnaire des tâches.", + L"Saisie de mot de passe annulée", MB_ICONINFORMATION | MB_OK); + } + } + return false; +} + +/** * Traitement du chargement de la liste des programmes au démarrage, et purge * des enregistrements périmés : **/ @@ -260,180 +498,137 @@ // Charge la liste des programmes lit_programmes(); - SYSTEMTIME time; - GetLocalTime(&time); - for(UINT i = 0; i < Programmes.size(); i++) { - // On garde les programmes, même si l'heure de début est passée - if (DiffTime(Programmes[i].fin, time) < 0) - // On efface les programmes dont l'heure de fin est passée - Programmes.erase(Programmes.begin()+i); + SYSTEMTIME localtime; + GetLocalTime(&localtime); + int i = 0; + while (i < (int)Programmes.size()) { + Programme & prog = Programmes[i]; + long diff = DiffTime(prog.fin, localtime); + + if (diff <= 0) { + // On efface les programmes sans répétitions dont l'heure de fin est passée + if (prog.repetition==0) { + Programmes.erase(Programmes.begin()+i); + programmation_modifiee = true; + continue; // L'enregistrement courant ayant été supprimé, on n'incrémente pas 'i' + } + + // Le cas échéant, on calcule la répétition suivante + // (mais ça a probablement déjà été fait ailleurs) + prog.ajustementDesRepetitions(localtime); + } + i++; } // Sauvegarde à nouveau la liste des programmes sauve_programmes(); } -void set_timer_record() +/** + * Trouver l'index de l'enregistrement programmé dont le nom est 'nom'. + * Retourne -1 si nom vide ou rien trouvé. + **/ +int trouve_prog_par_nom(LPCSTR nom) { - bool trouve = false; - next_record = 0; - SYSTEMTIME time; - int nbProgrammes = (int)Programmes.size(); - GetLocalTime(&time); - // On recherche le premier programme à enregistrer - for(int i=0; iDelete(pwszTaskName); + rest2 = temps_prochain_enregistrement(localtime); + reste = min(reste, rest2); - pITS->Release(); - return 0; -} + if (reste==DUREE_INFINIE) { + KillTimer(hMainWnd, TIMER_DELAYED_RECORD); + myprintf(L"Aucun événement d'enregistrement en attente\n"); + } else { + // Si l'heure de début est déjà passée, on lance un timer qui aboutit immédiatement + long timer = min(max(reste, USER_TIMER_MINIMUM), DUREE_RECALCUL_TIMERS*1000); -static int supprimer_taches(ListeTaches & taches_supprimer) { - for (size_t i=0; i= -(60-1) && diff <= -(60-20)) { + // On a incrémenté les secondes mais reculé de (60-20) à (60-1) secondes + if (tempres.wSecond < 20) + toAdd = 60; + } else if (diff >= -(60-1)*60 && diff <= -(60-20)*60) { + // On a incrémenté les minutes mais reculé de (60-20) à (60-1) minutes + if (tempres.wMinute < 20) + toAdd = 60*60; + } else if (diff == -(24-1)*60*60) { + // On a incrémenté les heures mais reculé de (24-1) heures + if (tempres.wHour < 1) + toAdd = 24*60*60; + } + } + } else { + // On a avancé dans le temps + + if (GetAsyncKeyState(VK_LBUTTON)<0 || GetAsyncKeyState(VK_SUBTRACT)<0 || GetAsyncKeyState(VK_DOWN)<0) { + // Traitement seulement si le bouton gauche de la souris ou une des touches + // '-' ou fleche en bas est couramment pressé : + if (diff >= (60-20) && diff <= (60-1)) { + // On a décrémenté les secondes mais avancé de (60-20) à (60-1) secondes + if (tempres.wSecond >= 60-20) + toAdd = -60; + } else if (diff >= (60-20)*60 && diff <= (60-1)*60) { + // On a décrémenté les minutes mais avancé de (60-20) à (60-1) minutes + if (tempres.wMinute >= 60-20) + toAdd = -60*60; + } else if (diff == (24-1)*60*60) { + // On a décrémenté les heures mais avancé de (24-1) heures + if (tempres.wHour >= 24-1) + toAdd = -24*60*60; + } + } + } + + if (toAdd != 0) { + AddTime(tempres, toAdd*1000); + SetCtlTimeDate(hDlg, dateID, timeID, tempres); + } + + result = tempres; } static void remplit_liste_programmes(HWND hListItem) { wchar_t buffer[64]; + static const LPCWSTR t_states[] = + { + L"Attente", + L"En cours", + L"Désactivé", + L"Interrompu", + L"Supplanté", + L"Terminé", + L"Erreur" + }; + LVITEM item = { LVIF_TEXT, // mask 0, 0, 0, 0, // iItem, iSubItem, state, stateMask buffer, // buffer _countof(buffer) // cchTextMax - // Le reste à zéro (implicite) + // le reste à zéro (implicite) }; int nbProgrammes = (int)Programmes.size(); @@ -489,12 +756,20 @@ ListView_SetItem(hListItem, &item); item.iSubItem = 2; - time2str(prog.debut, buffer, _countof(buffer)); + swprintf_s(buffer, _countof(buffer), L"%s %i/%02i à %ih%02i", + prog.startDoW(), prog.debut.wDay, prog.debut.wMonth, + prog.debut.wHour, prog.debut.wMinute); ListView_SetItem(hListItem, &item); item.iSubItem = 3; - time2str(prog.fin, buffer, _countof(buffer)); + swprintf_s(buffer, _countof(buffer), L"%ih%02i%s", + prog.fin.wHour, prog.fin.wMinute, + prog.repetition!=0 ? L"*" : L""); ListView_SetItem(hListItem, &item); + + item.iSubItem = 4; + wcscpy_s(buffer, _countof(buffer), t_states[prog.etat]); + ListView_SetItem(hListItem, &item); } } @@ -511,7 +786,6 @@ return apr_rien; } - /** * Mise à jour des cases à cocher correspondant à l'action "après enregistrement" * selon le contenu de la variable 'after'. @@ -523,107 +797,266 @@ } /** + * Coche ou décoche les cases liées aux répétitions : + **/ +static void SetCtlRepeat(HWND hDlg, int repeatBaseID, BYTE repetition) +{ + for (int i=0; i<7; i++) + CheckDlgButton(hDlg, repeatBaseID+i, (repetition & (1<pnom) + strcpy_s(pnom, bufSize, pwrk); + + // Supprimer les espaces de fin : + size_t strSize = strlen(pnom); + while (strSize>0 && pnom[strSize-1]<=' ') + pnom[--strSize] = 0; +} + +/** * Récupère les données de l'enregistrement dans la fenêtre. * Retourne 'true' si ces données sont valides, 'false' sinon. **/ -static bool dlgToProg(HWND hDlg, Programme & prog) +static bool dlgToProg(HWND hDlg, Programme & prog, bool modif) { - char buffer[256]; - int i; + int ixChaine; - (*(WORD *)&buffer[0]) = _countof(buffer)-1; - i = (int)SendDlgItemMessageA(hDlg, IDC_NAME, EM_GETLINE, 0, (LPARAM)buffer); - buffer[i] = 0; - strcpy_s(prog.nom, _countof(prog.nom), buffer); + // Récupérer la chaîne : + if ((ixChaine = (int)SendDlgItemMessage(hDlg, IDC_CHANNEL, CB_GETCURSEL, 0, 0))==CB_ERR) // index combo + return false; + if ((ixChaine = (int)SendDlgItemMessage(hDlg, IDC_CHANNEL, CB_GETITEMDATA, ixChaine, 0))<=0) // SID + return false; + if (!ixChaine_ok(ixChaine = trouve_chaine_par_sid(WORD(ixChaine)))) // index chaîne + return false; - int ixChaine = (int)SendDlgItemMessage(hDlg, IDC_CHANNEL, CB_GETCURSEL, 0, 0); - if (!ixChaine_ok(ixChaine)) - return false; + // Récupérer d'abord les dates, car un éventuel nom généré automatiquement se sert + // de la date de début : + GetCtlTimeDate(hDlg, IDC_DATE_START, IDC_TIME_START, prog.debut); + GetCtlTimeDate(hDlg, IDC_DATE_END, IDC_TIME_END, prog.fin); + prog.methode = (MethodeEnregistrement)SendDlgItemMessage(hDlg, IDC_METHOD, CB_GETCURSEL, 0, 0); + prog.audio = (AudioMode)SendDlgItemMessage(hDlg, IDC_AUDIO, CB_GETCURSEL, 0, 0); + const Chaine & canal = Canaux[ixChaine]; + NomProg nom; + // Récupérer le nom : + getDlgProgName(hDlg, nom, _countof(nom)); + + if (nom[0]==0) { + // le nom de la programmation est vide : + if (prog.nom[0]==0 || prog.nomAuto) { + // En générer un s'il n'y avait pas d'ancien nom, + // ou bien si celui-ci avait déjà été généré automatiquement : + unsigned ixSuffNom = 0; + // + do { + ixSuffNom++; + sprintf_s(nom, _countof(nom), ixSuffNom>1 ? "%s-%i/%02i_%u" : "%s-%i/%02i", + prog.isMultiplex() ? "Multiplex" : canal.nom, + prog.debut.wDay, prog.debut.wMonth, ixSuffNom); + } while (strcmp(nom, prog.nom)!=0 && trouve_prog_par_nom(nom)>=0); + strcpy_s(prog.nom, _countof(prog.nom), nom); + prog.nomAuto = true; + } + } else { + strcpy_s(prog.nom, _countof(prog.nom), nom); + prog.nomAuto = false; + } + prog.numeroChaine = canal.numeroChaine; prog.sidChaine = canal.SID; - GetCtlTimeDate(hDlg, IDC_DATE_START, IDC_TIME_START, prog.debut); - GetCtlTimeDate(hDlg, IDC_DATE_END, IDC_TIME_END, prog.fin); + prog.apres = GetCtlAfter(hDlg, IDC_AFTER_POWEROFF, IDC_AFTER); + prog.etat = IsDlgButtonChecked(hDlg, IDC_INACTIVE) == BST_CHECKED ? epr_inactif : epr_actif; - prog.methode = (MethodeEnregistrement)SendDlgItemMessage(hDlg, IDC_METHOD, CB_GETCURSEL, 0, 0); - prog.audio = (AudioMode)SendDlgItemMessage(hDlg, IDC_AUDIO, CB_GETCURSEL, 0, 0); - prog.after = GetCtlAfter(hDlg, IDC_AFTER_POWEROFF, IDC_AFTER); - // Ajoute l'indicatif pour la répétition - for (i=0; i<7; i++) { + prog.repetition = 0; + for (int i=0; i<7; i++) { if (IsDlgButtonChecked(hDlg, IDC_REP_BASE+i) == BST_CHECKED) - prog.repetition[i] = true; + prog.repetition |= 1<=_countof(canal.emis)) + return false; + + const Emission & emis = canal.emis[noEmis]; + + strcpy_s(prog.nom, _countof(prog.nom), emis.nom); + prog.sidChaine = canal.SID; + prog.numeroChaine = canal.numeroChaine; + prog.apres = apr_rien; + prog.methode = meth_TS; + prog.audio = audio_1; + prog.fin = emis.fin2; + prog.debut = emis.debut2; + prog.repetition = 0; + prog.tache = planif_sans; + prog.etat = epr_actif; + + return true; +} + +// Chargement des données de démarrage initialisant une nouvelle programmation : +static void InitNewRecord(HWND hDlg) +{ + SYSTEMTIME localtime; + + GetLocalTime(&localtime); + + localtime.wSecond = 0; + localtime.wMilliseconds = 0; + SetCtlTimeDate(hDlg, IDC_DATE_START, IDC_TIME_START, dtc_debut = localtime); + SetCtlTimeDate(hDlg, IDC_DATE_END, IDC_TIME_END, dtc_fin = localtime); + CtlSelChannel(GetDlgItem(hDlg, IDC_CHANNEL), ixChaineCourante); + SendDlgItemMessage(hDlg, IDC_METHOD, CB_SETCURSEL, meth_TS, 0); + SendDlgItemMessage(hDlg, IDC_AUDIO, CB_SETCURSEL, audio_1, 0); + SendDlgItemMessage(hDlg, IDC_NAME, WM_SETTEXT, 0, (LPARAM)L""); + + EnableWindow(GetDlgItem(hDlg, IDC_AUDIO), FALSE); + SetCtlAfter(hDlg, IDC_AFTER_POWEROFF, IDC_AFTER, apr_rien); + CheckDlgButton(hDlg, IDC_TACHE, BST_UNCHECKED); + CheckDlgButton(hDlg, IDC_INACTIVE, BST_UNCHECKED); + + SetCtlRepeat(hDlg, IDC_REP_BASE, 0); // Répétitions +} + +/** + * Remplissage d'une combo box de liste de chaînes actives. + **/ +static void remplit_liste_chaines(HWND hCtl) +{ + int nbChaines = (int)Canaux.size(); + + for (int i=0; i=0) { + // On ajoute le SID de la chaîne en tant que donnée associée : + // ceci servira à identifier la sélection, étant donné que l'index + // dans la combo box ne correspondra pas nécessairement à celui de la liste + // des chaînes, du fait des chaînes inactives possibles. + SendMessage(hCtl, CB_SETITEMDATA, res, canal.SID); + } + } } +} - Programmes.insert(Programmes.begin() + sel, prog); +/** + * Remplissage d'une combo-box de méthodes d'enregistement + **/ +void remplit_liste_methodes(HWND hCtl) +{ + SendMessage(hCtl, CB_ADDSTRING, 0, (LPARAM)TEXT("TS")); + SendMessage(hCtl, CB_ADDSTRING, 0, (LPARAM)TEXT("PS")); + if (allow_stream_record) + SendMessage(hCtl, CB_ADDSTRING, 0, (LPARAM)TEXT("Multiplex")); + SendMessage(hCtl, CB_SETCURSEL, meth_TS, 0); +} - PostMessage(hwndDlg, WM_APP, 0, 0); // Reconstruction de la liste - return 0; +/** + * Remplissage d'une combo-box d'items audio + **/ +void remplit_liste_audio(HWND hCtl) +{ + SendMessage(hCtl, CB_ADDSTRING, 0, (LPARAM)TEXT("Audio 1")); + SendMessage(hCtl, CB_ADDSTRING, 0, (LPARAM)TEXT("Audio 2")); + SendMessage(hCtl, CB_ADDSTRING, 0, (LPARAM)TEXT("Audio AC3")); + SendMessage(hCtl, CB_SETCURSEL, audio_1, 0); } -static INT_PTR CALLBACK RecordDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { +static void update_edit_state(HWND hDlg, int selection) +{ + NomProg nom; + bool edit = selection>=0 && Programmes[selection].etat!=epr_encours; - int sel = -1; - int n = (int)SendDlgItemMessage(hwndDlg, IDC_LIST_PROGRAMMES, LVM_GETITEMCOUNT, 0, 0); + EnableWindow(GetDlgItem(hDlg, IDC_MODIFY), edit); + EnableWindow(GetDlgItem(hDlg, IDC_REMOVE), edit); + + // Activation ou désactivation du bouton "Ajouter" selon le contenu de l'item + // d'édition de nom (ne doit pas être vide et ne doit pas déjà exister) : + getDlgProgName(hDlg, nom, _countof(nom)); + EnableWindow(GetDlgItem(hDlg, IDC_ADD), trouve_prog_par_nom(nom)<0); +} + +static INT_PTR CALLBACK RecordDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static HWND hListItem = NULL; int i; - static HWND hListItem = NULL; - static ListeTaches taches_supprimer; + static int selection = -1; // Index de l'item couramment sélectionné si >= 0 + static NomProg selProgNom = ""; // Nom de la sélection courante (pour pouvoir reconstituer celle-ci + // après modification ou tri de la liste) - for(i = 0; i < n; i++) { - if(SendDlgItemMessage(hwndDlg, IDC_LIST_PROGRAMMES, LVM_GETITEMSTATE, i, LVIS_SELECTED) & LVIS_SELECTED) - sel = i; - } - switch (uMsg) { case WM_INITDIALOG: { - hListItem = GetDlgItem(hwndDlg, IDC_LIST_PROGRAMMES); + selProgNom[0] = 0; + hListItem = GetDlgItem(hDlg, IDC_LIST_PROGRAMMES); ListView_SetExtendedListViewStyleEx(hListItem, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES, LVS_EX_FULLROWSELECT | LVS_EX_GRIDLINES); @@ -632,9 +1065,10 @@ int taille; } t_cols[] = { {L"Nom", 120}, - {L"Chaîne", 88}, - {L"Début", 118}, - {L"Fin", 118} + {L"Chaîne", 82}, + {L"Début", 128}, + {L"Fin", 48}, + {L"État", 68} }; for (i=0; i<_countof(t_cols); i++) { @@ -651,99 +1085,68 @@ } remplit_liste_programmes(hListItem); + remplit_liste_chaines(GetDlgItem(hDlg, IDC_CHANNEL)); // Chargement du combo de choix des chaînes + remplit_liste_methodes(GetDlgItem(hDlg, IDC_METHOD)); + remplit_liste_audio(GetDlgItem(hDlg, IDC_AUDIO)); - SYSTEMTIME time; - - GetLocalTime(&time); - - time.wSecond = 0; - time.wMilliseconds = 0; - SetCtlTimeDate(hwndDlg, IDC_DATE_START, IDC_TIME_START, time); - SetCtlTimeDate(hwndDlg, IDC_DATE_END, IDC_TIME_END, time); - - int nbChaines = (int)Canaux.size(); - - for (i=0; i= 0 && _ncode_(lParam)==NM_CLICK) { - const Programme & prog = Programmes[sel]; + if (_ncode_(lParam)==NM_CLICK) { + selection = ListView_GetSelectionMark(hListItem); + if (selection>=0) { + // Clic dans la liste des programmations + const Programme & prog = Programmes[selection]; - int ixChaine = trouve_chaine_par_sid(prog.sidChaine); + progToDlg(hDlg, prog); + dtc_debut = prog.debut; + dtc_fin = prog.fin; - if (ixChaine_ok(ixChaine)) { - SendDlgItemMessage(hwndDlg, IDC_CHANNEL, CB_SETCURSEL, ixChaine, 0); + SendDlgItemMessage(hDlg, IDC_METHOD, CB_SETCURSEL, prog.methode, 0); + SendDlgItemMessage(hDlg, IDC_AUDIO, CB_SETCURSEL, prog.audio, 0); - SendDlgItemMessageA(hwndDlg, IDC_NAME, WM_SETTEXT, 0, (LPARAM)prog.nom); - SetCtlTimeDate(hwndDlg, IDC_DATE_START, IDC_TIME_START, prog.debut); - SetCtlTimeDate(hwndDlg, IDC_DATE_END, IDC_TIME_END, prog.fin); + SetCtlAfter(hDlg, IDC_AFTER_POWEROFF, IDC_AFTER, prog.apres); - SendDlgItemMessage(hwndDlg, IDC_METHOD, CB_SETCURSEL, prog.methode, 0); - SendDlgItemMessage(hwndDlg, IDC_AUDIO, CB_SETCURSEL, prog.audio, 0); + CheckDlgButton(hDlg, IDC_TACHE, + prog.tache!=planif_sans ? BST_CHECKED : BST_UNCHECKED); - SetCtlAfter(hwndDlg, IDC_AFTER_POWEROFF, IDC_AFTER, prog.after); + // Active ou désactive le choix des pistes audio + PostMessage(hDlg, WM_COMMAND, MAKEWPARAM(IDC_METHOD, CBN_SELCHANGE), + LPARAM(GetDlgItem(hDlg, IDC_METHOD))); - CheckDlgButton(hwndDlg, IDC_TACHE, - prog.tache ? BST_CHECKED : BST_UNCHECKED); + // Coche les cases pour les répétitions : + SetCtlRepeat(hDlg, IDC_REP_BASE, prog.repetition); - // Active ou désactive le choix des pistes audio - PostMessage(hwndDlg, WM_COMMAND, (WPARAM) MAKELONG(IDC_METHOD, CBN_SELCHANGE), (LPARAM)GetDlgItem(hwndDlg, IDC_METHOD)); - - // Coche les cases pour les répétitions - for (i=0; i<7; i++) - CheckDlgButton(hwndDlg, IDC_REP_BASE+i, prog.repetition[i]); + strcpy_s(selProgNom, _countof(selProgNom), prog.nom); + return TRUE; } - return TRUE; + update_edit_state(hDlg, selection); } break; + case IDC_DATE_START: case IDC_TIME_START: if (_ncode_(lParam)==DTN_DATETIMECHANGE) { - SYSTEMTIME debut; - SYSTEMTIME fin; - - GetCtlTimeDate(hwndDlg, IDC_DATE_START, IDC_TIME_START, debut); - GetCtlTimeDate(hwndDlg, IDC_DATE_END, IDC_TIME_END, fin); - if (DiffTime(debut, fin) > 0) - SetCtlTimeDate(hwndDlg, IDC_DATE_END, IDC_TIME_END, debut); + GetCtlTimeDateChg(hDlg, IDC_DATE_START, IDC_TIME_START, dtc_debut); + GetCtlTimeDate(hDlg, IDC_DATE_END, IDC_TIME_END, dtc_fin); + if (DiffTime(dtc_debut, dtc_fin) > 0) + SetCtlTimeDate(hDlg, IDC_DATE_END, IDC_TIME_END, dtc_fin = dtc_debut); } break; + case IDC_DATE_END: case IDC_TIME_END: if (_ncode_(lParam)==DTN_DATETIMECHANGE) { - SYSTEMTIME debut; - SYSTEMTIME fin; - - GetCtlTimeDate(hwndDlg, IDC_DATE_START, IDC_TIME_START, debut); - GetCtlTimeDate(hwndDlg, IDC_DATE_END, IDC_TIME_END, fin); - if (DiffTime(debut, fin) > 0) - SetCtlTimeDate(hwndDlg, IDC_DATE_START, IDC_TIME_START, fin); + GetCtlTimeDate(hDlg, IDC_DATE_START, IDC_TIME_START, dtc_debut); + GetCtlTimeDateChg(hDlg, IDC_DATE_END, IDC_TIME_END, dtc_fin); + if (DiffTime(dtc_debut, dtc_fin) > 0) + SetCtlTimeDate(hDlg, IDC_DATE_START, IDC_TIME_START, dtc_debut = dtc_fin); } } return FALSE; @@ -753,98 +1156,139 @@ case IDC_METHOD: if (HIWORD(wParam) == CBN_SELCHANGE) { - int index = (int)SendDlgItemMessage(hwndDlg, IDC_METHOD, CB_GETCURSEL, 0, 0); - if (index == 0 || index ==2){ - EnableWindow(GetDlgItem( hwndDlg, IDC_AUDIO), FALSE); - } - if (index == 1) { - EnableWindow(GetDlgItem( hwndDlg, IDC_AUDIO), TRUE); - } + EnableWindow(GetDlgItem(hDlg, IDC_AUDIO), + SendDlgItemMessage(hDlg, IDC_METHOD, CB_GETCURSEL, 0, 0)==meth_PS); + return TRUE; } - return TRUE; + break; + case IDC_NAME: + if (HIWORD(wParam) == EN_CHANGE) { + update_edit_state(hDlg, selection); + return TRUE; + } + break; case IDC_REMOVE: - if (sel != -1) { - ListView_DeleteItem(hListItem, sel); - Programme &prog = Programmes[sel]; + if (selection >= 0 && Programmes[selection].etat!=epr_encours) { + ListView_DeleteItem(hListItem, selection); - if (prog.tache != planif_sans) { - char *tache=new char[strlen(prog.nom)+1]; - strcpy_s(tache, strlen(prog.nom)+1, prog.nom); - taches_supprimer.push_back(tache); - } - Programmes.erase(Programmes.begin() + sel); + // Suppression éventuelle tâche antérieure portant ce nom : + supprime_une_tache(Programmes[selection].nom); + + Programmes.erase(Programmes.begin() + selection); + finalize_prog_change(); } - return TRUE; case IDC_MODIFY: - if (sel != -1) { - Programme prog = Programmes[sel]; - Programmes.erase(Programmes.begin() + sel); - if(add_programme(hwndDlg, sel, taches_supprimer)) - Programmes.insert(Programmes.begin() + sel, prog); - return TRUE; + if (selection >= 0) { + Programme & prog = Programmes[selection]; + Programme newprog = prog; + + if (dlgToProg(hDlg, newprog, true)) { + // Récupération et vérification données du dialogue + + if (prog.etat == epr_encours) + MessageBox(hDlg, L"Cet enregistrement est actuellement en cours.\n" + L"Utilisez la commande \"Stopper l'enregistrement dans...\"" + L" pour y apporter des modifications maintenant.", + L"Erreur lors de la modification", MB_ICONERROR); + else { + // Suppression éventuelle tâche antérieure portant l'ancien nom : + supprime_une_tache(prog.nom); + + prog = newprog; + + strcpy_s(selProgNom, _countof(selProgNom), prog.nom); + prog.ajoutTacheAvecLogon(hDlg); + + finalize_prog_change(); + //set_timer_record_end(); + } + } } - return FALSE; + return TRUE; case IDC_ADD: { - int n = ListView_GetItemCount(hListItem); + Programme prog; - add_programme(hwndDlg, (sel + n + 1) % (n + 1), taches_supprimer); + // Récupération des données de l'enregistrement depuis la boîte de dialogue + if (!dlgToProg(hDlg, prog, false)) + return FALSE; + + strcpy_s(selProgNom, _countof(selProgNom), prog.nom); + prog.ajoutTacheAvecLogon(hDlg); + Programmes.push_back(prog); + + finalize_prog_change(); return TRUE; } - case IDOK: - // sauvegarder le changement + case IDC_NEW: + // Réinitialiser les données de programmation + InitNewRecord(hDlg); + selection = -1; + selProgNom[0] = 0; + update_edit_state(hDlg, selection); + return TRUE; - supprimer_taches(taches_supprimer); - ajouter_taches(hwndDlg); - sauve_programmes(); + case IDC_CURRENT_PROGRAM: { + // Charger les données de l'émission en cours + Programme prog; - // ... passage à travers - case IDCANCEL: + if (chaineToProg(ixChaineCourante, 0, prog)) { + progToDlg(hDlg, prog); + dtc_debut = prog.debut; + dtc_fin = prog.fin; + selection = -1; + selProgNom[0] = 0; + update_edit_state(hDlg, selection); + } + return TRUE; } - lit_programmes(); - set_timer_record(); - DestroyWindow(hwndDlg); - return TRUE; - case IDC_AFTER_POWEROFF: // Coche le bouton arrêtant PouchinTV si on a demandé l'arrêt de l'ordinateur - if (IsDlgButtonChecked(hwndDlg, IDC_AFTER_POWEROFF) == BST_CHECKED) - CheckDlgButton(hwndDlg, IDC_AFTER, BST_CHECKED); + if (IsDlgButtonChecked(hDlg, IDC_AFTER_POWEROFF) == BST_CHECKED) + CheckDlgButton(hDlg, IDC_AFTER, BST_CHECKED); return TRUE; case IDC_AFTER: // Décoche le bouton arrêtant le PC si on ne souhaite pas arrêter PouchinTV - if (IsDlgButtonChecked(hwndDlg, IDC_AFTER) == BST_UNCHECKED) - CheckDlgButton(hwndDlg, IDC_AFTER_POWEROFF, BST_UNCHECKED); + if (IsDlgButtonChecked(hDlg, IDC_AFTER) == BST_UNCHECKED) + CheckDlgButton(hDlg, IDC_AFTER_POWEROFF, BST_UNCHECKED); return TRUE; + + case IDOK: + DestroyWindow(hDlg); + return TRUE; } - return FALSE; + break; case WM_CLOSE: - lit_programmes(); - set_timer_record(); - DestroyWindow(hwndDlg); + DestroyWindow(hDlg); return TRUE; case WM_NCDESTROY: + // Fermeture de la boîte de dialogue : on efface le mot de passe + SecureZeroMemory(mdp, sizeof(mdp)); + hRecordDlg = NULL; ShowCursor(FALSE); // Rétablit la disparition temporisée du curseur - return FALSE; + return TRUE; case WM_APP: // Reconstruction de la liste des enregistrements programmés après changement : ListView_DeleteAllItems(hListItem); remplit_liste_programmes(hListItem); - ListView_SetItemState(hListItem, sel, LVIS_SELECTED, LVIS_SELECTED); + selection = trouve_prog_par_nom(selProgNom); + if (selection>=0) + ListView_SetItemState(hListItem, selection, LVIS_SELECTED, LVIS_SELECTED); + update_edit_state(hDlg, selection); return TRUE; - default: - return FALSE; } + + return FALSE; } /** @@ -873,42 +1317,334 @@ return false; } -static INT_PTR CALLBACK DelayedStopDialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { - SYSTEMTIME time; - UINT delayed; - int grab; +/** + * Transformation d'un horaire de fin en temps restant + **/ +static SYSTEMTIME EndTimeToRemTime(SYSTEMTIME time) +{ + SYSTEMTIME localtime; + LONG wMillisec; + GetLocalTime(&localtime); + wMillisec = DiffTime(time, localtime); + + // S'assurer que l'heure de fin tombe bien dans les 24 heures qui suivent : + #define ONEDAY (24*60*60*1000) + while (wMillisec<0) + wMillisec += ONEDAY; + while (wMillisec>=ONEDAY) + wMillisec -= ONEDAY; + #undef ONEDAY + + time.wMilliseconds = WORD(wMillisec % 1000); + wMillisec /= 1000; + time.wSecond = WORD(wMillisec % 60); + wMillisec /= 60; + time.wMinute = WORD(wMillisec % 60); + wMillisec /= 60; + time.wHour = WORD(wMillisec); + return time; +} + +/** + * Transformation d'un temps restant en horaire de fin + **/ +static SYSTEMTIME RemTimeToEndTime(SYSTEMTIME time) +{ + LONG wMillisec = ((time.wHour*60 + time.wMinute)*60 + time.wSecond)*1000 + time.wMilliseconds; + + GetLocalTime(&time); + AddTime(time, wMillisec); + return time; +} + +struct DelayEditData { + HWND hDlg; + HWND hEnrCombo; + HWND hTimeCtl; + int grab; // index de l'enregistrement en cours + int comboSel; // item couramment sélectionné dans la combo box + Enregistrement * penreg; // pointeur enregistrement à modifier + SYSTEMTIME refTime; // Contenu du contrôle de temps, soit en tant qu'heure de fin, + // soit en tant que temps restant, selon le contenu de + // 'useEndTime' (définit le temps de référence) + bool useEndTime; // vrai si le temps mémorisé (refTime) est l'heure de fin + bool dispEndTime; // vrai si le contrôle affiche l'heure de fin + bool delayActive; // vrai si la temporisation sera activée + bool dlgModified; // vrai si on a changé quelque chose + + void remplitCombo(); + void load(bool initActive); // charger les données de l'enregistrement N° 'grab' + void setCtlTime(); // Transférer le temps de référence dans le contrôle, selon le mode courant + bool needTimer() const {return delayActive && dispEndTime != useEndTime;} + void toEndTime(void); // convertir en mode "heure de fin" + void apply(void); // appliquer les changements + void setChangedState(bool changed); // Indique si le contenu du dialogue a été modifié ou pas + void setActiveState(bool active); // Modifie l'état "actif" de la fin d'enregistrement programmée + void recordEnded(void); +}; + +// Chargement du combo des chaînes en cours d'enregistrement : +void DelayEditData::remplitCombo() +{ + wchar_t buf[64]; + LPCSTR nom; + int res, i, sel=0; + + comboSel = 0; + for (i = 0; i<_countof(enregistrements_actuels); i++) { + const Enregistrement & enreg = enregistrements_actuels[i]; + + if (!enreg.recording()) + continue; + nom = enreg.channelName(); + swprintf_s(buf, _countof(buf), L"%S", *nom=='*' ? "[ multiplex ]" : nom); + res = SendMessage(hEnrCombo, CB_ADDSTRING, 0, LPARAM(buf)); + SendMessage(hEnrCombo, CB_SETITEMDATA, res, i); + if (i==grab) + sel = res; + } + + comboSel = SendMessage(hEnrCombo, CB_SETCURSEL, sel, 0); +} + +// Chargement des données de l'enregistrement 'grab'. +// Si 'initActive' = true, le dialogue sera préparé pour activation si aucune +// temporisation n'existait auparavant pour cet enregistrement +// (afin d'éviter à l'utilisateur d'avoir à cliquer sur la case d'activation +// lors de l'ouverture du dialogue). Il restera néanmoins nécessaire de valider +// ce changement. +void DelayEditData::load(bool initActive) +{ + if (grab<0) + grab = 0; + penreg = &enregistrements_actuels[grab]; + + CheckDlgButton(hDlg, IDC_DELAY_BY_REMTIME, BST_CHECKED); + dispEndTime = false; + if (!penreg->recording()) { + recordEnded(); + } else { + EnableWindow(GetDlgItem(hDlg, IDC_DELAY_ACTIVE), TRUE); + if (penreg->apres != apr_null) { + setActiveState(true); + SetCtlAfter(hDlg, IDC_AFTER_POWEROFF, IDC_AFTER, penreg->apres); + refTime = penreg->fin; + useEndTime = true; + setChangedState(false); + } else { + setActiveState(initActive); + GetLocalTime(&refTime); + // Indiquer temps restant = 1/2 heure par défaut. + refTime.wHour = refTime.wSecond = refTime.wMilliseconds = 0; + refTime.wMinute = 30; + useEndTime = false; + setChangedState(initActive); + } + } + CheckDlgButton(hDlg, IDC_DELAY_ACTIVE, delayActive); + setCtlTime(); +} + +// Modifie l'état "actif" de la fin d'enregistrement programmée +void DelayEditData::setActiveState(bool active) +{ + static const int t_id[] = + {IDC_DELAYED_STOP, IDC_DELAY_LABEL, IDC_DELAY_BY_REMTIME, IDC_DELAY_BY_ENDTIME, + IDC_AFTER_RECORD, IDC_AFTER, IDC_AFTER_POWEROFF, IDC_AFTER_NOTE}; + + delayActive = active; + for (int i=0; i<_countof(t_id); i++) + EnableWindow(GetDlgItem(hDlg, t_id[i]), delayActive); +} + +// Fonction appelée si l'enregistrement se termine pendant que le dialogue +// est ouvert : +void DelayEditData::recordEnded(void) +{ + setActiveState(false); + EnableWindow(GetDlgItem(hDlg, IDC_DELAY_ACTIVE), FALSE); +} + +// Indique si le contenu du dialogue a été modifié ou pas +void DelayEditData::setChangedState(bool changed) +{ + dlgModified = changed; + EnableWindow(GetDlgItem(hDlg, IDC_APPLY), dlgModified); +} + +void DelayEditData::toEndTime(void) +{ + if (useEndTime) { + // Si on utilise déjà l'heure de fin, on effectue une double conversion + // pour s'assurer que celle-ci se trouve bien dans le futur, et non pas + // plus tôt dans la journée (étant donné que le dialogue ne peut pas + // définir la date). + refTime = EndTimeToRemTime(refTime); + useEndTime = false; + } + if (!useEndTime) { + refTime = RemTimeToEndTime(refTime); + useEndTime = true; + } +} + +void DelayEditData::setCtlTime() +{ + if (dispEndTime==useEndTime) + DateTime_SetSystemtime(hTimeCtl, GDT_VALID, &refTime); + else { + SYSTEMTIME tmptime = (dispEndTime ? RemTimeToEndTime : EndTimeToRemTime)(refTime); + + DateTime_SetSystemtime(hTimeCtl, GDT_VALID, &tmptime); + } +}; + +// Application des modifications : +void DelayEditData::apply(void) +{ + if (penreg->recording()) { // Toujours en train d'enregistrer ? + ApresEnregistrement apres = delayActive ? + GetCtlAfter(hDlg, IDC_AFTER_POWEROFF, IDC_AFTER) : + apr_null; + + toEndTime(); + penreg->fin = refTime; + penreg->apres = apres; + + // Rechercher si l'arrêt retardé résultait d'une programmation, + // afin de mettre à jour celle-ci en même temps, le cas échéant : + int ixProg = trouve_prog_par_nom(penreg->nom); + + if (ixProg>=0) { + Programme & prog = Programmes[ixProg]; + + if (prog.repetition!=0) { + // La programmation comporte des répétisions : + // on se contente de la "déconnecter" de l'enregistrement, lui permettant + // ainsi d'être reportée intégralement à son occurrence suivante. + prog.etat = epr_supplante; + } else { + if (apres == apr_null) { + // On a annulé l'arrêt d'enregistrement (celui-ci + // peut donc se poursuivre indéfiniment) + prog.etat = epr_supplante; + } else { + // On a modifié l'heure ou l'action de fin : on reporte les + // modifications dans la programmation, de sorte qu'un arrêt + // impromptu éventuel du programme suivi d'une relance + // (plantage ?) reprennne le reliquat ainsi. + prog.fin = refTime; + prog.apres = apres; + } + record_list_updated(); + } + programmation_modifiee = true; + } + + setChangedState(false); + PostMessage(hMainWnd, WM_TIMECHANGE, 0, 0); + } +} + +// Gestion du dialogue des arrêts d'enregistrement temporisés : +static INT_PTR CALLBACK DelayedStopDialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static DelayEditData edat = {0}; // tout à zéro, false ou NULL à l'initialisation + switch (uMsg) { + case WM_INITDIALOG: - GetLocalTime(&time); + edat.hDlg = hDlg; + edat.hEnrCombo = GetDlgItem(hDlg, IDC_RECORD); + edat.hTimeCtl = GetDlgItem(hDlg, IDC_DELAYED_STOP); + edat.grab = getGrabberChaine(ixChaineCourante); + edat.load(true); + edat.remplitCombo(); // Chargement du combo des enregistrements - time.wHour = 0; - time.wMinute = 0; - time.wSecond = 0; - time.wMilliseconds = 0; - SendDlgItemMessage(hwndDlg, IDC_DELAYED_STOP, DTM_SETSYSTEMTIME, GDT_VALID, (LPARAM)&time); + // Création d'un timer battant à la seconde pour la mise à jour du contrôle de saisie + // de l'heure (selon son mode d'affichage), ainsi que la détection des + // enregistrements terminés. + SetTimer(hDlg, TIMER_DELAYED_STOP, 1000, NULL); return TRUE; - case WM_COMMAND: - switch(LOWORD(wParam)) { - case IDOK: - grab = getGrabberChaine(ixChaineCourante); - if (grab>=0) { - enregistrements_actuels[grab].setAfter(GetCtlAfter(hwndDlg, IDC_AFTER_POWEROFF, IDC_AFTER)); - KillTimer(hMainWnd, TIMER_RECORD_END_BASE+grab); - SendDlgItemMessage(hwndDlg, IDC_DELAYED_STOP, DTM_GETSYSTEMTIME, 0, (LPARAM)&time); - delayed = ((time.wHour*60 + time.wMinute)*60 + time.wSecond)*1000; + case WM_TIMER: + if (!edat.penreg->recording()) // Tester si l'enregistrement se termine à ce moment + edat.recordEnded(); + if (edat.needTimer()) + edat.setCtlTime(); + return TRUE; - SetTimer(hMainWnd, TIMER_RECORD_END_BASE+grab, delayed, NULL); + case WM_COMMAND: { + switch(wParam) { + + case _cmd_(IDC_RECORD, CBN_SELCHANGE): { + int newSel = SendMessage(edat.hEnrCombo, CB_GETCURSEL, 0, 0); + + if (newSel!=CB_ERR && newSel!=edat.comboSel) { + if (edat.dlgModified) { + switch (MessageBox(hDlg, + L"Vous avez apporté des modifications à la programmation de la chaîne en cours.\n" + L"Désirez-vous valider ces modifications ?", + L"Changement de chaîne", + MB_YESNOCANCEL)) { + + case IDCANCEL: + SendMessage(edat.hEnrCombo, CB_SETCURSEL, WPARAM(edat.comboSel), 0); + return TRUE; + + case IDYES: + edat.apply(); + case IDNO: ; + } + } + edat.comboSel = newSel; + edat.grab = SendMessage(edat.hEnrCombo, CB_GETITEMDATA, WPARAM(newSel), 0); + edat.load(false); } - case IDCANCEL: - EndDialog(hwndDlg, 0); + return TRUE; } + + case _cmd_(IDC_DELAY_BY_REMTIME, BN_CLICKED): + case _cmd_(IDC_DELAY_BY_ENDTIME, BN_CLICKED): + edat.dispEndTime = IsDlgButtonChecked(hDlg, IDC_DELAY_BY_ENDTIME)==BST_CHECKED; + edat.setCtlTime(); return TRUE; + + case _cmd_(IDC_DELAY_ACTIVE, BN_CLICKED): + edat.setActiveState(IsDlgButtonChecked(hDlg, IDC_DELAY_ACTIVE)==BST_CHECKED); + edat.setChangedState(true); + return TRUE; + + case _cmd_(IDC_AFTER, BN_CLICKED): + case _cmd_(IDC_AFTER_POWEROFF, BN_CLICKED): + edat.setChangedState(true); + return TRUE; + + case _cmd_(IDC_APPLY, BN_CLICKED): + edat.apply(); + return TRUE; + + case _cmd_(IDOK, BN_CLICKED): + edat.apply(); + // ... + case _cmd_(IDCANCEL, BN_CLICKED): + KillTimer(hDlg, TIMER_DELAYED_STOP); + EndDialog(hDlg, 0); + return TRUE; } - // ... - default: - return FALSE; + break; } + + case WM_NOTIFY: + if (_ncode_(lParam)==DTN_DATETIMECHANGE) { + DateTime_GetSystemtime(edat.hTimeCtl, &edat.refTime); + // Le mode d'affichage utilisé sert maintenant de référence : + edat.useEndTime = edat.dispEndTime; + edat.setChangedState(true); + } + return TRUE; } + + return FALSE; } /** @@ -922,14 +1658,38 @@ } /** - * Fonction à appeler en cas de changement externe de la liste des enregistrements programmés. + * Fonction à appeler en cas de changement externe possible de la liste des enregistrements programmés. + * Assure la sauvegarde des modification et la mise à jour de la liste dans la fenêtre de dialogue + * si celle-ci est ouverte. **/ -void update_record_list() +void record_list_updated() { - if (hRecordDlg) - PostMessage(hRecordDlg, WM_APP, 0, 0); // Reconstruction de la liste + if (programmation_modifiee) { + // Tri de la liste : + std::sort(Programmes.begin(), Programmes.end()); + + if (hRecordDlg) + PostMessage(hRecordDlg, WM_APP, 0, 0); // Reconstruction de la liste si couramment affichée + + // Sauvegarde de la liste des programmes + sauve_programmes(); + } } +/** + * Finalisation d'un changement dans les enregistrements programmés. + **/ +void finalize_prog_change() +{ + programmation_modifiee = true; + + // Mise à jour du timer des enregistrements : + set_timer_record(); + + // Mise à jour de l'affichage, et sauvegarde dans le fichier : + record_list_updated(); +} + // Trouver la piste audio la mieux adaptée au choix de l'utilisateur lors de la programmation : int selection_son(const un_son * sons, int nb_son, AudioMode audio) { @@ -958,148 +1718,173 @@ return 0; } -// Traitement des démarrages d'enregistrements. -// Retourne le numéro de l'enregistrement qui a démarré, ou bien -1 si aucun n'a pu démarrer. -int do_record_start() +/** + * Démarre tous les enregistrements qui devraient l'être à cet instant précis. + * Retourne 'true' si au moins un changement de nature à affecter les menus + * ou la programmation d'enregistrement a eu lieu + **/ +static bool do_record_start(const SYSTEMTIME & localtime) { - Programme & prog = Programmes[next_record]; // Description de l'enregistrement à effectuer - // Chercher l'index de la chaîne dont le numéro NIT correspond à celui demandé pour - // l'enregistrement : - int ixChaine = trouve_chaine_par_sid(prog.sidChaine); + long frequence_courante = 0; + int ixChaine = -1; + bool changement = false; - if (ixChaine<0) - return -1; // si on n'en a pas trouvé + // Déterminer si on enregistre quelque chose actuellement, et si oui, quelle est la + // fréquence utilisée. + WORD sidChaine = recording(); - const Chaine & canal = Canaux[ixChaine]; - // - // Vérifier qu'on peut enregistrer : aucun enregistrement ne doit être déjà en cours, ou - // bien l'enregistrement en cours doit concerner le même multiplex. - long frequence_courante = Canaux[ixChaineCourante].frequence; + if (sidChaine!=0) { + ixChaine = trouve_chaine_par_sid(sidChaine); - if (recording()!=0 && canal.frequence != frequence_courante) - return -1; // si on ne peut pas enregistrer - - if (canal.frequence != frequence_courante) { - change_frequence(canal.frequence); - ixChaineCourante = ixChaine; - ixSonCourant = 0; + if (ixChaine>=0) + frequence_courante = Canaux[ixChaine].frequence; } - if (!IsMinimized(hMainWnd) && suspend_minimized) - rebranche(); + int nbProgrammes = (int)Programmes.size(); - int ixSon = selection_son(canal.son, canal.nb_son, prog.audio); // utilisé seulement en PS pour le moment + // Passer en revue les programmations définies afin de démarrer TOUS les enregistrements qui + // devraient être en cours et qui peuvent être démarrés. + for (int i=0; i= DUREE_GRACE_QUIT*60*1000 || diff == -1)) { - // On entre ici si aucun (autre) enregistrement n'est en cours, et si aucun - // enregistrement n'est programmé pour débuter dans un délai défini par - // la constante DUREE_GRACE_QUIT. + // Traiter tous les enregistrements en cours qui devraient être arrêtés maintenant : + ApresEnregistrement after = do_record_stop(localtime); - // Traiter l'arrêt du programme et/ou de l'ordinateur s'il y a lieu : - switch (after) { + // Traiter tous les enregistrements en cours qui devraient être démarrés maintenant : + if (do_record_start(localtime)) + after = apr_rien; - case apr_quitter: - // Arrêt de pouchinTV - SetTimer(hMainWnd, TIMER_QUIT, DUREE_QUIT, NULL); - break; + // Traiter l'arrêt du programme et/ou de l'ordinateur s'il y a lieu : + switch (after) { - case apr_eteindrePC: - // Arrêt de l'ordinateur - SetTimer(hMainWnd, TIMER_SHUTDOWN, DUREE_QUIT, NULL); - } + case apr_null: + return false; + + case apr_quitter: + // Arrêt de pouchinTV + SetTimer(hMainWnd, TIMER_QUIT, DUREE_QUIT, NULL); + break; + + case apr_eteindrePC: + // Arrêt de l'ordinateur + SetTimer(hMainWnd, TIMER_SHUTDOWN, DUREE_QUIT, NULL); } + + return true; } Modified: trunk/recprog.h =================================================================== --- trunk/recprog.h 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/recprog.h 2007-12-14 16:03:59 UTC (rev 106) @@ -28,19 +28,20 @@ #pragma once #include "base.h" +#include "ini.h" enum MethodeEnregistrement { meth_TS, // 0 - meth_PS // 1 -#if USE_RECORD_STREAM - , meth_multiplex // 2 -#endif // #if USE_RECORD_STREAM + meth_PS, // 1 + meth_multiplex // 2 }; enum ApresEnregistrement { apr_rien, // 0 apr_quitter, // 1 - apr_eteindrePC // 2 + apr_eteindrePC, // 2 + apr_null = -1 // utilisé dans les enregistrements en cours, pour indiquer + // qu'aucun horaire de fin n'est associé à un enregistrement }; enum AudioMode { @@ -55,31 +56,95 @@ planif_ajout // 2: planifiée (à ajouter au planificateur lors de la validation) }; -struct Programme { - MethodeEnregistrement methode; - AudioMode audio; - ApresEnregistrement after; - PlanificateurTache tache; - SYSTEMTIME debut; - SYSTEMTIME fin; - WORD numeroChaine; - WORD sidChaine; - char nom[256]; - // Tableau contenant les répétitions des enregistrements +enum EtatProgramme { + epr_actif, + epr_encours, + epr_inactif, // désactivé par l'utilisateur + epr_interrompu, // manuellement arrêté après démarrage + epr_supplante, // si heure de fin modifiée après démarrage + epr_termine, // heure de fin atteinte + epr_erreur, + epr_null = -1 // pseudo-valeur pour dire "ne pas modifier" +}; + +typedef char NomProg[64]; // Type pour nom donné à une programmation + +// Structure commune aux programmations d'enregistrement et aux enregistrements en cours : +class RecordInfo +{ +public: + NomProg nom; // Nom de la programmation si applicable + WORD sidChaine; // SID de la chaîne enregistrée + WORD numeroChaine; // Numéro de la chaîne + ApresEnregistrement apres; + MethodeEnregistrement methode; + AudioMode audio; + SYSTEMTIME fin; // Date & heure de fin + + // Mini-constructeur : + RecordInfo() {memset(this, 0, sizeof(*this)); apres = apr_null;} +}; + +class Programme : public RecordInfo +{ +public: + SYSTEMTIME debut; // Date & heure de début + + // Registre de bits contenant les répétitions des enregistrements : // 0: Dimanche, 1: Lundi, ...., 6: Samedi - bool repetition[7]; - bool planifie; // Indique si un timer a été lancé pour ce programme - // + BYTE repetition; + + PlanificateurTache tache; + EtatProgramme etat; + bool nomAuto; // 'true' si nom généré automatiquement + + // Mini-constructeur : + Programme(); + // Méthodes : + // Vérification de la validité de la programmation - bool verifie() const; - // + // (mettre 'modif' à 'true' si on est en train de modifier une programmation) + bool verifie(bool modif) const; + + // Calcul du temps restant avant début enregistrement en millisecondes. + // L'heure de référence (en principe l'heure système) doit être passée en paramètre. + long temps_restant(const SYSTEMTIME & time) const; + + // Ajustement des données si répétitions programmées et horaire de fin dépassé + // L'heure de référence (en principe l'heure système) doit être passée en paramètre. + // Retourne 'true' si un ajustement a eu lieu + bool ajustementDesRepetitions(const SYSTEMTIME & time); + // Ajout de cette programmation aux tâches programmées (retourne 'false' si échec) bool ajouterTacheProgrammee(LPCWSTR username, LPCWSTR motdepasse) const; + // + // Ajout de la programmation si nécessaire, incluant identification + bool ajoutTacheAvecLogon(HWND hDlg); + + // Génération du nom de tâche programmée correspondant + void genereNomTache(LPWSTR pstr, size_t bufSize) const; + + // Obtenir le nom du jour de la semaine du début de l'enregistrement : + LPCWSTR startDoW() const + {return debut.wDayOfWeek<7 ? Tbl_DoW[debut.wDayOfWeek] : L"?";} + + // Retourne 'true' si la programmation est couramment active : + bool active() const + {return etat==epr_actif || etat==epr_encours;} + + // Retourne 'true' si intersection temporelle avec la programmation 'p2' + bool overlap(const Programme & p2) const; + + // Retourne 'true' si on a affaire à une programmation d'enregistrement de multiplex + bool isMultiplex() const + {return methode==meth_multiplex;} + + // Utilisé par la fonction std::sort : + bool operator < (const Programme & p2) const; }; extern std::vector Programmes; -extern int next_record; /** * Traitement du chargement de la liste des programmes au démarrage, et purge @@ -87,6 +152,9 @@ **/ void init_programmation(); +/** + * (Re)calculer le timer de la prochaine transition d'enregistrement + **/ void set_timer_record(); /** @@ -108,10 +176,52 @@ void delayed_stop_dialog(); /** - * Fonction à appeler en cas de changement externe de la liste des enregistrements programmés. + * Finalisation d'un changement dans les enregistrements programmés. **/ -void update_record_list(); +void finalize_prog_change(); -int do_record_start(); +/** + * Fonction à appeler en cas de changement externe possible de la liste des enregistrements programmés. + * Assure la sauvegarde des modification et la mise à jour de la liste dans la fenêtre de dialogue + * si celle-ci est ouverte. + **/ +void record_list_updated(); -void do_record_stop(int grab); +//void do_record_stop(int grab); + +/** + * Exécution de toutes les transitions d'enregistrements qui devraient être assurées à cet instant. + * Retourne 'true' si au moins une transition (démarrage, arrêt, erreur...) a eu lieu. + **/ +bool do_record_events(); + +/** + * Transfert d'une description d'émission dans une structure d'enregistrement programmé + **/ +bool chaineToProg(int ixChaine, BYTE noEmis, Programme & prog); + +/** + * Trouver l'index de l'enregistrement programmé dont le nom est 'nom'. + * Retourne -1 si nom vide ou rien trouvé. + **/ +int trouve_prog_par_nom(LPCSTR nom); + +/** + * Fonction qui retourne la différence time1 - time2, convertie en millisecondes. + **/ +long DiffTime(SYSTEMTIME time1, SYSTEMTIME time2); + +/** + * Fonction qui ajoute 'add' millisecondes au temps 'time. + **/ +void AddTime(SYSTEMTIME & time, long add); + +/** + * Remplissage d'une combo-box de méthodes d'enregistement + **/ +void remplit_liste_methodes(HWND hCtl); + +/** + * Remplissage d'une combo-box d'items audio + **/ +void remplit_liste_audio(HWND hCtl); \ No newline at end of file Modified: trunk/res.rc =================================================================== --- trunk/res.rc 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/res.rc 2007-12-14 16:03:59 UTC (rev 106) @@ -102,9 +102,6 @@ MENUITEM "Enregistrements p&rogrammés...\tP", IDM_DELAYED_RECORD MENUITEM "Enregistrer la chaîne en &PS\tCtrl+Espace", IDM_RECORD_CHANNEL_PS MENUITEM "Enregistrer la chaîne en &TS\tCtrl+T", IDM_RECORD_CHANNEL_TS -#if defined(USE_RECORD_STREAM) - MENUITEM "Enregistrer le &multiplex\tCtrl+M",IDM_RECORD_STREAM -#endif MENUITEM SEPARATOR MENUITEM "&Stopper l'enregistrement\tCtrl+Entrée", IDM_STOP_RECORD MENUITEM "Stopper l'enregistrement &dans...\tS", IDM_DELAYED_STOP @@ -176,8 +173,11 @@ "Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,37,262,10 GROUPBOX "Audio",IDC_STATIC,7,69,276,30 CONTROL "Utiliser l'AC3 par défaut",IDC_AC3_DEF,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,81,262,10 - GROUPBOX "Divers",IDC_STATIC,7,111,276,30 - CONTROL "Afficher le nom de l'émission sous MSN",IDC_MSN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,123,262,10 + GROUPBOX "Avancé",IDC_STATIC,7,111,276,30 + CONTROL "Permettre l'enregistrement intégral du multiplex courant",IDC_ALLOW_STREAM_RECORD, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,125,262,10 + GROUPBOX "Divers",IDC_STATIC,7,153,276,30 + CONTROL "Afficher le nom de l'émission sous MSN",IDC_MSN,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,166,262,10 END IDD_FOLDERS DIALOGEX 0, 0, 290, 214 @@ -273,27 +273,30 @@ COMBOBOX IDC_CHANNEL,7,34,109,12,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP COMBOBOX IDC_METHOD,124,34,52,18,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP COMBOBOX IDC_AUDIO,180,34,53,12,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - LTEXT "Nom de l'enregistrement :",IDC_STATIC,7,51,89,12,SS_CENTERIMAGE - EDITTEXT IDC_NAME,124,51,109,12,ES_AUTOHSCROLL - CONTROL "Fermer Pouchin TV Mod après l'enregistrement",IDC_AFTER, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,68,197,13 + LTEXT "Nom de l'enregistrement :",IDC_STATIC,14,51,89,12,SS_CENTERIMAGE + EDITTEXT IDC_NAME,99,51,134,12,ES_AUTOHSCROLL + CONTROL "Ajouter aux tâches planifées de Windows",IDC_TACHE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,65,153,12 + CONTROL "Désactivé",IDC_INACTIVE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,184,65,49,12 + GROUPBOX "Après l'enregistrement :",IDC_AFTER_RECORD,7,77,226,41 + CONTROL "Fermer Pouchin TV Mod",IDC_AFTER,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,85,102,13 CONTROL "Éteindre la lumière en partant",IDC_AFTER_POWEROFF, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,83,197,12 - CONTROL "Ajouter le programme aux tâches planifées de Windows",IDC_TACHE, - "Button",BS_AUTOCHECKBOX | WS_TABSTOP,7,97,197,12 - GROUPBOX "Répéter tous les...",IDC_REPETITIONS,7,109,286,33,WS_DISABLED - CONTROL "Lundi",IDC_REP_LUNDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,17,118,60,10 - CONTROL "Mardi",IDC_REP_MARDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,84,118,60,10 - CONTROL "Mercredi",IDC_REP_MERCREDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,151,118,60,10 - CONTROL "Jeudi",IDC_REP_JEUDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,218,118,60,10 - CONTROL "Vendredi",IDC_REP_VENDREDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,17,130,60,10 - CONTROL "Samedi",IDC_REP_SAMEDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,84,130,60,10 - CONTROL "Dimanche",IDC_REP_DIMANCHE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,151,130,60,10 - PUSHBUTTON "Ajouter",IDC_ADD,240,7,53,14 - PUSHBUTTON "Modifier",IDC_MODIFY,240,24,53,14 - PUSHBUTTON "Supprimer",IDC_REMOVE,240,41,53,14 - DEFPUSHBUTTON "OK",IDOK,240,75,53,14 - PUSHBUTTON "Annuler",IDCANCEL,240,93,53,14 + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,120,85,110,12 + CTEXT "Note : en cas d'enregistrements simultanés, ceci s'applique au dernier enregistrement stoppé.",IDC_AFTER_NOTE,37,98,159,16 + PUSHBUTTON "Nouveau",IDC_NEW,240,7,53,14 + PUSHBUTTON "Émission en cours",IDC_CURRENT_PROGRAM,240,24,53,20,BS_MULTILINE + PUSHBUTTON "Ajouter",IDC_ADD,240,52,53,14 + PUSHBUTTON "Modifier",IDC_MODIFY,240,69,53,14 + PUSHBUTTON "Supprimer",IDC_REMOVE,240,86,53,14 + DEFPUSHBUTTON "Fermer",IDOK,240,107,53,14 + GROUPBOX "Répéter tous les...",IDC_REPETITIONS,7,120,286,22,WS_DISABLED + CONTROL "Lundi",IDC_REP_LUNDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,130,32,10 + CONTROL "Mardi",IDC_REP_MARDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,47,130,30,10 + CONTROL "Mercredi",IDC_REP_MERCREDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,81,130,39,10 + CONTROL "Jeudi",IDC_REP_JEUDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,126,130,32,10 + CONTROL "Vendredi",IDC_REP_VENDREDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,160,130,44,10 + CONTROL "Samedi",IDC_REP_SAMEDI,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,205,130,36,10 + CONTROL "Dimanche",IDC_REP_DIMANCHE,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,245,130,46,10 CONTROL "",IDC_LIST_PROGRAMMES,"SysListView32",LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS | LVS_ALIGNLEFT | LVS_NOSORTHEADER | WS_BORDER,0,145,300,109 END @@ -308,19 +311,27 @@ PUSHBUTTON "Annuler",IDCANCEL,89,54,54,14 END -IDD_DELAYED_STOP DIALOGEX 100, 100, 180, 82 +IDD_DELAYED_STOP DIALOGEX 100, 100, 166, 148 STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Stopper l'enregistrement dans..." FONT 8, "MS Shell Dlg", 400, 0, 0x1 BEGIN - CONTROL "",IDC_DELAYED_STOP,"SysDateTimePick32",DTS_UPDOWN | WS_TABSTOP | 0x8,65,7,48,12 - CTEXT "hh:mm:ss",IDC_STATIC,65,20,48,10 - CONTROL "Fermer Pouchin TV Mod après l'enregistrement",IDC_AFTER, - "Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,7,32,166,10 + RTEXT "Chaîne enregistrée :",IDC_STATIC,7,9,70,8 + COMBOBOX IDC_RECORD,83,7,76,37,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + CONTROL "Temporisation active (après validation)",IDC_DELAY_ACTIVE, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,14,24,138,10 + CONTROL "",IDC_DELAYED_STOP,"SysDateTimePick32",DTS_UPDOWN | WS_TABSTOP | 0x8,7,38,48,12 + LTEXT "hh:mm:ss",IDC_DELAY_LABEL,9,51,36,10 + CONTROL "Spécifie le temps restant",IDC_DELAY_BY_REMTIME,"Button",BS_AUTORADIOBUTTON,62,39,98,10 + CONTROL "Spécifie l'heure de fin",IDC_DELAY_BY_ENDTIME,"Button",BS_AUTORADIOBUTTON,62,51,98,10 + GROUPBOX "Après l'enregistrement :",IDC_AFTER_RECORD,7,62,152,63 + CONTROL "Fermer Pouchin TV Mod",IDC_AFTER,"Button",BS_AUTOCHECKBOX | BS_TOP | BS_MULTILINE | WS_TABSTOP,14,73,138,10 CONTROL "Éteindre la lumière en partant",IDC_AFTER_POWEROFF, - "Button",BS_AUTOCHECKBOX | BS_TOP | WS_TABSTOP,7,46,166,10 - DEFPUSHBUTTON "OK",IDOK,30,61,54,14 - PUSHBUTTON "Annuler",IDCANCEL,94,61,54,14 + "Button",BS_AUTOCHECKBOX | BS_TOP | WS_TABSTOP,14,84,138,10 + LTEXT "Note : en cas d'enregistrements simultanés, ceci s'applique au dernier enregistrement stoppé.",IDC_AFTER_NOTE,30,97,106,27 + DEFPUSHBUTTON "OK",IDOK,7,127,46,14 + PUSHBUTTON "Appliquer",IDC_APPLY,60,127,46,14,WS_DISABLED + PUSHBUTTON "Annuler",IDCANCEL,113,127,46,14 END IDD_CLOSE_CONFIRM DIALOGEX 100, 100, 150, 75 @@ -440,9 +451,9 @@ IDD_DELAYED_STOP, DIALOG BEGIN LEFTMARGIN, 7 - RIGHTMARGIN, 173 + RIGHTMARGIN, 159 TOPMARGIN, 7 - BOTTOMMARGIN, 75 + BOTTOMMARGIN, 141 END IDD_CLOSE_CONFIRM, DIALOG @@ -490,9 +501,6 @@ VK_SPACE, IDM_RECORD_CHANNEL_PS, VIRTKEY, CONTROL, NOINVERT "T", IDM_RECORD_CHANNEL_TS, VIRTKEY, CONTROL, NOINVERT "S", IDM_DELAYED_STOP, VIRTKEY, NOINVERT -#if defined(USE_RECORD_STREAM) - "M", IDM_RECORD_STREAM, VIRTKEY, CONTROL, NOINVERT -#endif "X", IDM_SCREENSHOT, VIRTKEY, CONTROL, NOINVERT VK_RETURN, IDM_STOP_RECORD, VIRTKEY, CONTROL, NOINVERT VK_ADD, IDM_VOL_AUG, VIRTKEY, NOINVERT Modified: trunk/resource.h =================================================================== --- trunk/resource.h 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/resource.h 2007-12-14 16:03:59 UTC (rev 106) @@ -2,6 +2,7 @@ // Microsoft Visual C++ generated include file. // Used by res.rc // + #define IDI_PROG_ICON 101 #define IDI_ICON_REC 102 #define IDB_BITMAP 105 @@ -43,6 +44,8 @@ #define IDC_SIGNAL_STRENGTH 1018 #define IDC_VERSION 1021 #define IDC_ABOUT_ICON 1022 +#define IDC_NEW 1023 +#define IDC_APPLY 1024 #define IDC_ADD 1025 #define IDC_REMOVE 1026 #define IDC_DATE_START 1027 @@ -52,17 +55,20 @@ #define IDC_TIME_END 1031 #define IDC_LIST_PROGRAMMES 1032 #define IDC_START 1033 +#define IDC_CURRENT_PROGRAM 1034 #define IDC_SUSPEND 1035 #define IDC_SYSTEM_TRAY 1036 #define IDC_AC3_DEF 1037 #define IDC_MSN 1038 +#define IDC_ALLOW_STREAM_RECORD 1039 #define IDC_MPEG2_DIR_VIEW 1040 #define IDC_MPEG2_DIR 1041 #define IDC_SCREENSHOTS_DIR_VIEW 1042 #define IDC_SCREENSHOTS_DIR 1043 #define IDC_CHANNEL 1045 #define IDC_NAME 1046 -#define IDC_RECORD 1046 +#define IDC_RECORD 1047 +#define IDC_INACTIVE 1048 #define IDC_METHOD 1052 #define IDC_AUDIO 1053 #define IDC_AUDIO_EPG 1057 @@ -90,10 +96,18 @@ #define IDC_RENUMBER 1108 #define IDC_RENUM_SEQ 1109 #define IDC_RENUM_REST 1110 +#define IDC_DELAY_ACTIVE 1111 +#define IDC_DELAY_BY_REMTIME 1112 +#define IDC_DELAY_BY_ENDTIME 1113 +#define IDC_AFTER_RECORD 1114 +#define IDC_AFTER_NOTE 1115 +#define IDC_DELAY_LABEL 1116 #define MY_TRAY_ICON_ID 20001 #define MY_TRAY_ICON_MESSAGE 20002 + #define IDM_CHAINES_BASE 39000 // Offset de base pour changement chaîne #define IDM_PISTES_BASE 39500 // Offset de base pour choix bande sonore +#define IDM_STOP_RECORD_BASE 39900 // Offset de base pour arrêt enregistrement #define IDM_QUIT 40001 #define IDM_ABOUT 40002 #define IDM_CONFIG 40003 @@ -131,11 +145,7 @@ #define IDM_DELAYED_RECORD 40044 #define IDM_RECORD_CHANNEL_TS 40045 #define IDM_RECORD_CHANNEL_PS 40046 - -#if defined(USE_RECORD_STREAM) - #define IDM_RECORD_STREAM 40047 -#endif - +#define IDM_RECORD_STREAM 40047 #define IDM_STOP_RECORD 40048 #define IDM_DELAYED_STOP 40049 @@ -157,7 +167,7 @@ #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 133 #define _APS_NEXT_COMMAND_VALUE 40069 -#define _APS_NEXT_CONTROL_VALUE 1116 +#define _APS_NEXT_CONTROL_VALUE 1117 #define _APS_NEXT_SYMED_VALUE 121 #endif #endif Modified: trunk/settings.cpp =================================================================== --- trunk/settings.cpp 2007-12-14 14:40:58 UTC (rev 105) +++ trunk/settings.cpp 2007-12-14 16:03:59 UTC (rev 106) @@ -1097,9 +1097,6 @@ ListView_SortItemsEx(hListItem, LVCompareProc, lParam); PropSheet_Changed(GetParent(hDlg), hDlg); iItemEdit = -1; -#ifdef MODIFY_CHANNELS_BY_CONTROLS - EnableModifications(hDlg, FALSE); -#endif } break; @@ -1147,9 +1144,6 @@ sauve_chaines(); // sauvegarde les chaînes dans le ini iItemEdit = -1; -#ifdef MODIFY_CHANNELS_BY_CONTROLS - EnableModifications(hDlg, FALSE); -#endif break; } } return FALSE; @@ -1261,6 +1255,7 @@ set_check(hDlg, IDC_SYSTEM_TRAY, minimize_system_tray); set_check(hDlg, IDC_AC3_DEF, use_ac3); set_check(hDlg, IDC_MSN, use_msn); + set_check(hDlg, IDC_ALLOW_STREAM_RECORD, allow_stream_record); return TRUE; case WM_COMMAND: @@ -1269,6 +1264,7 @@ case _cmd_(IDC_SYSTEM_TRAY, BN_CLICKED): case _cmd_(IDC_AC3_DEF, BN_CLICKED): case _cmd_(IDC_MSN, BN_CLICKED): + case _cmd_(IDC_ALLOW_STREAM_RECORD, BN_CLICKED): PropSheet_Changed(GetParent(hDlg), hDlg); return 0; } @@ -1281,6 +1277,7 @@ minimize_system_tray = get_check(hDlg, IDC_SYSTEM_TRAY); use_ac3 = get_check(hDlg, IDC_AC3_DEF); use_msn = get_check(hDlg, IDC_MSN); + allow_stream_record = get_check(hDlg, IDC_ALLOW_STREAM_RECORD); niData.update_state(); return psn_result(hDlg, PSNRET_NOERROR); }