winamp/Src/Plugins/Input/in_mod/mikamp/src/Info.c

603 lines
19 KiB
C
Raw Normal View History

2024-09-24 12:54:57 +00:00
#include "api.h"
#include "main.h"
#include "resource.h"
#include <commctrl.h>
#include "../../winamp/wa_ipc.h"
#include <strsafe.h>
static BOOL CALLBACK infoProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
static BOOL CALLBACK tabProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
static void OnSelChanged(HWND hwndDlg);
INFOBOX *infobox_list = NULL;
int config_info_x = 0, config_info_y = 0;
BOOL config_track = FALSE;
// =====================================================================================
void infobox_delete(HWND hwnd)
// =====================================================================================
// This function is called with the handle of the infobox to destroy. It unloads the
// module if appropriate (care must be take not to unload a module which is in use!).
{
INFOBOX *cruise, *old;
old = cruise = infobox_list;
while(cruise)
{
if(cruise->hwnd == hwnd)
{
if(cruise == infobox_list)
infobox_list = cruise->next;
else old->next = cruise->next;
// Destroy the info box window, then unload the module, *if*
// the module is not actively playing!
info_killseeker(hwnd);
DestroyWindow(hwnd);
if (cruise->dlg.module!=mf && cruise->dlg.ownModule)
Unimod_Free(cruise->dlg.module);
free(cruise->dlg.suse);
free(cruise);
return;
}
old = cruise;
cruise = cruise->next;
}
}
// =====================================================================================
MPLAYER *get_player(UNIMOD *othermf)
// =====================================================================================
// Checks the current module against the one given. if they match the MP is returned,
// else it returns NULL.
{
if (mf == othermf)
return mp;
return NULL;
}
// =====================================================================================
static void infoTabInit(HWND hwndDlg, UNIMOD *m, DLGHDR *pHdr)
// =====================================================================================
{
DWORD dwDlgBase = GetDialogBaseUnits();
int cxMargin = LOWORD(dwDlgBase) / 4,
cyMargin = HIWORD(dwDlgBase) / 8;
TC_ITEM tie;
int tabCounter;
// Add a tab for each of the three child dialog boxes.
// and lock the resources for the child frames that appear within.
tie.mask = TCIF_TEXT | TCIF_IMAGE;
tie.iImage = -1;
tabCounter = 0;
if(m->numsmp)
{
tie.pszText = WASABI_API_LNGSTRING(IDS_SAMPLES);
TabCtrl_InsertItem(pHdr->hwndTab, tabCounter, &tie);
pHdr->apRes[tabCounter] = WASABI_API_CREATEDIALOGPARAM(IDD_SAMPLES, hwndDlg, tabProc, IDD_SAMPLES);
SendMessage(mikmod.hMainWindow,WM_WA_IPC,(WPARAM)pHdr->apRes[tabCounter],IPC_USE_UXTHEME_FUNC);
SetWindowPos(pHdr->apRes[tabCounter], HWND_TOP, pHdr->left+1, pHdr->top+15, 0, 0, SWP_NOSIZE);
ShowWindow(pHdr->apRes[tabCounter++], SW_HIDE);
}
if(m->numins)
{
tie.pszText = WASABI_API_LNGSTRING(IDS_INSTRUMENTS);
TabCtrl_InsertItem(pHdr->hwndTab, tabCounter, &tie);
pHdr->apRes[tabCounter] = WASABI_API_CREATEDIALOGPARAM(IDD_INSTRUMENTS, hwndDlg, tabProc, IDD_INSTRUMENTS);
SendMessage(mikmod.hMainWindow,WM_WA_IPC,(WPARAM)pHdr->apRes[tabCounter],IPC_USE_UXTHEME_FUNC);
SetWindowPos(pHdr->apRes[tabCounter], HWND_TOP, pHdr->left+1, pHdr->top+15, 0, 0, SWP_NOSIZE);
ShowWindow(pHdr->apRes[tabCounter++], SW_HIDE);
}
if(m->comment && m->comment[0])
{
tie.pszText = WASABI_API_LNGSTRING(IDS_COMMENT);
TabCtrl_InsertItem(pHdr->hwndTab, tabCounter, &tie);
pHdr->apRes[tabCounter] = WASABI_API_CREATEDIALOGPARAM(IDD_COMMENT, hwndDlg, tabProc, CEMENT_BOX);
SendMessage(mikmod.hMainWindow,WM_WA_IPC,(WPARAM)pHdr->apRes[tabCounter],IPC_USE_UXTHEME_FUNC);
SetWindowPos(pHdr->apRes[tabCounter], HWND_TOP, pHdr->left+1, pHdr->top+15, 0, 0, SWP_NOSIZE);
ShowWindow(pHdr->apRes[tabCounter++], SW_HIDE);
}
// Simulate selection of the LAST item
TabCtrl_SetCurSel(pHdr->hwndTab, tabCounter-1);
OnSelChanged(hwndDlg);
}
// =====================================================================================
static BOOL CALLBACK tabProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
// =====================================================================================
// This is the callback procedure used by each of the three forms under the
// tab control on the Module info dialog box (sample, instrument, comment
// info forms).
{
switch (uMsg)
{ case WM_INITDIALOG:
{
HWND hwndLB;
DLGHDR *pHdr = (DLGHDR *)GetWindowLong(GetParent(hwndDlg), GWL_USERDATA);
UNIMOD *m = pHdr->module;
char sbuf[10280] = {0};
switch(lParam)
{ case IDD_SAMPLES:
{
uint x;
hwndLB = GetDlgItem(hwndDlg, IDC_SAMPLIST);
for (x=0; x<m->numsmp; x++)
{
StringCbPrintfA(sbuf, sizeof(sbuf), "%02d: %s",x+1, m->samples[x].samplename ? m->samples[x].samplename : "");
SendMessage(hwndLB, LB_ADDSTRING, 0, (LPARAM) sbuf);
}
SendMessage(hwndLB, LB_SETCURSEL, 0, 0);
tabProc(hwndDlg, WM_COMMAND, (WPARAM)((LBN_SELCHANGE << 16) + IDC_SAMPLIST), (LPARAM)hwndLB);
}
return TRUE;
case IDD_INSTRUMENTS:
{
uint x;
hwndLB = GetDlgItem(hwndDlg, IDC_INSTLIST);
for (x=0; x<m->numins; x++)
{
StringCbPrintfA(sbuf, sizeof(sbuf), "%02d: %s",x+1, m->instruments[x].insname ? m->instruments[x].insname : "");
SendMessage(hwndLB, LB_ADDSTRING, 0, (LPARAM) sbuf);
}
SendMessage(hwndLB, LB_SETCURSEL, 0, 0);
tabProc(hwndDlg, WM_COMMAND, (WPARAM)((LBN_SELCHANGE << 16) + IDC_INSTLIST), (LPARAM)hwndLB);
}
return TRUE;
case CEMENT_BOX:
if(m->comment && m->comment[0])
{
uint x,i;
hwndLB = GetDlgItem(hwndDlg, CEMENT_BOX);
// convert all CRs to CR/LF pairs. That's the way the edit box likes them!
for(x=0, i=0; x<strlen(m->comment) && i < sizeof(sbuf)-1; x++)
{
sbuf[i++] = m->comment[x];
if(m->comment[x]==0x0d && m->comment[x+1]!=0x0a)
sbuf[i++] = 0x0a;
}
sbuf[i] = 0;
SetWindowText(hwndLB, sbuf);
}
return TRUE;
}
}
break;
case WM_COMMAND:
if(HIWORD(wParam) == LBN_SELCHANGE)
{ // Processes the events for the sample and instrument list boxes, namely updating
// the samp/inst info upon a WM_COMMAND issuing a listbox selection change.
int moo = SendMessage((HWND)lParam, LB_GETCURSEL, 0, 0);
char sbuf[1024] = {0}, st1[128] = {0}, st2[64] = {0}, st3[64] = {0};
char tmp1[32] = {0}, tmp2[32] = {0}, tmp3[32] = {0};
DLGHDR *pHdr = (DLGHDR *)GetWindowLong(GetParent(hwndDlg), GWL_USERDATA);
UNIMOD *m = pHdr->module;
switch (LOWORD(wParam))
{ case IDC_INSTLIST:
{ INSTRUMENT *inst = &m->instruments[moo];
uint x;
size_t cnt;
char *sbuf_p;
// --------------------
// Part 1: General instrument header info
// default volume, auto-vibrato, fadeout (in that order).
{
StringCbPrintfA(sbuf, sizeof(sbuf), "%d%%\n%s\n%s", (inst->globvol * 400) / 256,
WASABI_API_LNGSTRING_BUF((inst->vibdepth ? IDS_YES : IDS_NO),tmp1,sizeof(tmp1)/sizeof(*tmp1)),
WASABI_API_LNGSTRING_BUF((inst->volfade ? IDS_YES : IDS_NO),tmp2,sizeof(tmp2)/sizeof(*tmp2)));
SetWindowText(GetDlgItem(hwndDlg, IDC_INSTHEAD), sbuf);
}
//(inst->nnatype == NNA_CONTINUE) ? "Continue" : (inst->nnatype == NNA_OFF) ? "Off" : (inst->nnatype == NNA_FADE) ? "Fade" : "Cut");
// --------------------
// Part 2: The instrument envelope info (vol/pan/pitch)
// Wow this is ugly, but it works: Make a set of strings that have the
// '(loop / sustain)' string. Tricky, cuz the '/' is only added if it
// is needed of course.
if(inst->volflg & (EF_LOOP | EF_SUSTAIN))
{
StringCbPrintfA(st1, sizeof(st1), "(%s%s%s)",
(inst->volflg & EF_LOOP) ? WASABI_API_LNGSTRING(IDS_LOOP) : "",
((inst->volflg & EF_LOOP) && (inst->volflg & EF_SUSTAIN)) ? " / " : "",
(inst->volflg & EF_SUSTAIN) ? WASABI_API_LNGSTRING_BUF(IDS_SUSTAIN,tmp1,sizeof(tmp1)/sizeof(*tmp1)) : "");
} else st1[0] = 0;
if(inst->panflg & (EF_LOOP | EF_SUSTAIN))
{
StringCbPrintfA(st2, sizeof(st2), "(%s%s%s)",
(inst->panflg & EF_LOOP) ? WASABI_API_LNGSTRING(IDS_LOOP) : "",
((inst->panflg & EF_LOOP) && (inst->panflg & EF_SUSTAIN)) ? " / " : "",
(inst->panflg & EF_SUSTAIN) ? WASABI_API_LNGSTRING_BUF(IDS_SUSTAIN,tmp1,sizeof(tmp1)/sizeof(*tmp1)) : "");
} else st2[0] = 0;
if(inst->pitflg & (EF_LOOP | EF_SUSTAIN))
{
StringCchPrintfA(st3,sizeof(st3), "(%s%s%s)",
(inst->pitflg & EF_LOOP) ? WASABI_API_LNGSTRING(IDS_LOOP) : "",
((inst->pitflg & EF_LOOP) && (inst->pitflg & EF_SUSTAIN)) ? " / " : "",
(inst->pitflg & EF_SUSTAIN) ? WASABI_API_LNGSTRING_BUF(IDS_SUSTAIN,tmp1,sizeof(tmp1)/sizeof(*tmp1)) : "");
} else st3[0] = 0;
{
StringCbPrintfA(sbuf, sizeof(sbuf), "%s %s\n%s %s\n%s %s",
WASABI_API_LNGSTRING_BUF(((inst->volflg & EF_ON) ? IDS_ON : IDS_OFF),tmp1,sizeof(tmp1)/sizeof(*tmp1)),
st1[0] ? st1 : "",
WASABI_API_LNGSTRING_BUF(((inst->panflg & EF_ON) ? IDS_ON : IDS_OFF),tmp2,sizeof(tmp2)/sizeof(*tmp2)),
st2[0] ? st2 : "",
WASABI_API_LNGSTRING_BUF(((inst->pitflg & EF_ON) ? IDS_ON : IDS_OFF),tmp3,sizeof(tmp3)/sizeof(*tmp3)),
st3[0] ? st3 : "");
}
SetWindowText(GetDlgItem(hwndDlg, IDC_INSTENV), sbuf);
// --------------------
// Part 3: List of samples used by this instrument!
// the trick here is that that we have to figure out what samples are used from the
// sample index table in inst->samplenumber.
memset(pHdr->suse,0,m->numsmp*sizeof(BOOL));
for(x=0; x<120; x++)
if(inst->samplenumber[x] != 65535)
pHdr->suse[inst->samplenumber[x]] = 1;
sbuf[0] = 0; cnt = sizeof(sbuf)/sizeof(*sbuf);
sbuf_p = sbuf;
for (x=0; x<m->numsmp; x++)
{
if(pHdr->suse[x])
{
StringCbPrintfExA(sbuf_p, cnt, &sbuf_p, &cnt, 0, "%02d: %s\r\n",x+1, m->samples[x].samplename);
}
}
if (cnt < sizeof(sbuf)/sizeof(*sbuf))
{
sbuf[sizeof(sbuf)/sizeof(*sbuf) - cnt - 2] = 0; // cut off the final CR/LF set
}
SetWindowText(GetDlgItem(hwndDlg, TB_SAMPLELIST), sbuf);
}
break;
case IDC_SAMPLIST:
{ UNISAMPLE *samp = &m->samples[moo];
EXTSAMPLE *es = NULL;
if(m->extsamples) es = &m->extsamples[moo];
// Display sampe header info...
// Length, Format, Quality, Looping, Auto-vibrato, Volume, Panning (in that order).
{
char yn[64] = {0}, pp[64] = {0};
StringCbPrintfA(sbuf, sizeof(sbuf), "%d %s\n%d %s\n%s %s\n%s\n%s\n%d\n%d",
samp->length * (samp->format&SF_16BITS ? 2 : 1), WASABI_API_LNGSTRING_BUF(IDS_BYTES, tmp1, sizeof(tmp1)/sizeof(*tmp1)),
samp->speed, WASABI_API_LNGSTRING_BUF((m->flags&UF_XMPERIODS ? IDS_FINETUNE : (samp->format&SF_SIGNED ? IDS_HZ_SIGNED : IDS_HZ_UNSIGNED)),tmp2,sizeof(tmp2)/sizeof(*tmp2)),
samp->format & SF_16BITS ? "16" : "8", WASABI_API_LNGSTRING_BUF(IDS_BITS, tmp3, sizeof(tmp3)/sizeof(*tmp3)),
WASABI_API_LNGSTRING_BUF((samp->flags&SL_LOOP ? ( samp->flags&SL_BIDI ? IDS_PING_PONG : (samp->flags&SL_REVERSE ? IDS_REVERSE : IDS_FORWARD )) : samp->flags&SL_SUSTAIN_LOOP ? ( samp->flags&SL_SUSTAIN_BIDI ? IDS_SUSTAIN_PING_PONG : IDS_SUSTAIN ) : IDS_NONE),pp,sizeof(pp)/sizeof(*pp)),
WASABI_API_LNGSTRING_BUF(((es && es->vibdepth) ? IDS_YES : IDS_NO),yn,sizeof(yn)/sizeof(*yn)),
samp->volume, samp->panning);
}
SetWindowText(GetDlgItem(hwndDlg, IDC_SAMPINFO), sbuf);
}
break;
}
}
break;
}
return 0;
}
// =====================================================================================
static void OnSelChanged(HWND hwndDlg)
// =====================================================================================
{
DLGHDR *pHdr = (DLGHDR *) GetWindowLong(hwndDlg, GWL_USERDATA);
int iSel = TabCtrl_GetCurSel(pHdr->hwndTab);
if(pHdr->hwndDisplay) ShowWindow(pHdr->hwndDisplay,SW_HIDE);
ShowWindow(pHdr->apRes[iSel],SW_SHOW);
pHdr->hwndDisplay = pHdr->apRes[iSel];
// Note to self: Despite their inhernet use in interfaces, coding tab controls
// apparently REALLY sucks, and it should never ever be done again by myself
// or anyone else whom I respect as a sane individual and I would like to have
// remain that way. As for me, it is too late. Bruhahahaha!K!J!lkjgkljASBfkJBdglkn.
}
// =====================================================================================
static void CALLBACK UpdateInfoRight(HWND hwnd, UINT uMsg, UINT ident, DWORD systime)
// =====================================================================================
{
char str[256] = {0};
DLGHDR *pHdr = (DLGHDR *)GetWindowLong(hwnd, GWL_USERDATA);
MPLAYER *mp;
// Player info update .. BPM, sngspeed, position, row, voices.
// Only update if our mf struct is the same as the one currently loaded into the player.
if ((mp = get_player(pHdr->module)) == NULL)
{
// clean up
if (pHdr->inUse)
{
UNIMOD *m = pHdr->module;
pHdr->inUse = FALSE;
StringCbPrintfA(str, sizeof(str), WASABI_API_LNGSTRING(IDS_X_X_X_OF_X_NOT_PLAYING),
m->inittempo, m->initspeed, m->numpos);
SetDlgItemText(hwnd, IDC_INFORIGHT, str);
if (pHdr->seeker)
{
if (pHdr->seeker != mp)
Player_Free(pHdr->seeker);
pHdr->seeker = NULL;
}
}
// "track song" mode
if (mf && IsDlgButtonChecked(hwnd, IDC_TRACK)==BST_CHECKED)
{
SendMessage(hwnd, WM_USER+10, 0, 0);
infoDlg(GetParent(hwnd), mf, GetActiveWindow()==hwnd, FALSE);
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
}
else
{
MPLAYER *seeker;
long acv;
if (!pHdr->inUse)
{
assert(pHdr->seeker == NULL);
pHdr->inUse = TRUE;
// create our new player instance specifically for seeking
if (!(config_playflag & CPLAYFLG_SEEKBYORDERS))
{
if ((pHdr->seeker = Player_Dup(mp)) == NULL)
return;
// steal statelist from the original player
// (this will not require special handling,
// because of a smart allocation system)
pHdr->seeker->statelist = mp->statelist;
pHdr->seeker->statecount = mp->statecount;
}
else pHdr->seeker = NULL;
}
// Seek to our new song time, using a bastardized version of Player_SetPosTime code:
if (pHdr->seeker)
{
long curtime = mikmod.GetOutputTime() * 64;
seeker = pHdr->seeker;
if (seeker->statelist)
{
int t = 0;
while (t<seeker->statecount &&
seeker->statelist[t].curtime &&
curtime>=seeker->statelist[t].curtime)
t++;
if (t)
Player_Restore(seeker, t - 1);
else Player_Cleaner(seeker);
}
else Player_Cleaner(seeker);
while(!seeker->ended && seeker->state.curtime<curtime)
Player_PreProcessRow(seeker, NULL);
}
else seeker = mp;
// Display all the goodie info we have collected:
// ---------------------------------------------
acv = Mikmod_GetActiveVoices(mp->vs->md);
if (acv > pHdr->maxv) pHdr->maxv = acv;
StringCbPrintfA(str, sizeof(str), WASABI_API_LNGSTRING(IDS_X_X_X_OF_X_X_OF_X_X_OF_X),
seeker->state.bpm, seeker->state.sngspd, seeker->state.sngpos,
seeker->mf->numpos, seeker->mf->positions[seeker->state.sngpos],
seeker->state.patpos, seeker->state.numrow, acv, pHdr->maxv);
SetWindowText(GetDlgItem(hwnd,IDC_INFORIGHT), str);
}
}
// =====================================================================================
void infoDlg(HWND hwnd, UNIMOD *m, BOOL activate, BOOL primiary)
// =====================================================================================
{
INFOBOX *box;
HWND dialog, hwndPrev;
char str[256] = {0};
if (!m) return;
//
// create local dataplace
//
box = (INFOBOX*)calloc(1, sizeof(INFOBOX));
if (!box) return;
box->dlg.left = 7;
box->dlg.top = 168;
box->dlg.module = m;
box->dlg.ownModule = primiary;
box->next = infobox_list;
infobox_list = box;
//
// create dialog
//
hwndPrev = GetActiveWindow();
box->hwnd = dialog = WASABI_API_CREATEDIALOG(IDD_ID3EDIT, hwnd, infoProc);
box->dlg.hwndTab = GetDlgItem(dialog, IDC_TAB);
SetWindowLong(dialog, GWL_USERDATA, (LONG)&box->dlg);
SetDlgItemText(dialog, IDC_ID3_FN, m->filename);
// IDC_INFOLEFT contains static module information:
// File Size, Length (in mins), channels, samples, instruments.
StringCbPrintfA(str, sizeof(str), WASABI_API_LNGSTRING(IDS_X_BTYES_X_OF_X_MINUTES),
m->filesize, m->songlen/60000,(m->songlen%60000)/1000, m->numchn, m->numsmp, m->numins);
SetDlgItemText(dialog, IDC_INFOLEFT, str);
SetDlgItemText(dialog, IDC_TITLE, m->songname);
SetDlgItemText(dialog, IDC_TYPE, m->modtype);
// IDC_INFORIGHT - contains player information
StringCbPrintfA(str, sizeof(str), WASABI_API_LNGSTRING(IDS_X_X_X_OF_X_NOT_PLAYING),
m->inittempo, m->initspeed, m->numpos);
SetDlgItemText(dialog, IDC_INFORIGHT, str);
// pHdr->suse is a samples-used block, allocated if this module uses
// instruments, and used to display the sampels that each inst uses
if (m->numins)
box->dlg.suse = (BOOL*)calloc(m->numsmp, sizeof(BOOL));
CheckDlgButton(dialog, IDC_TRACK, config_track ? BST_CHECKED : BST_UNCHECKED);
infoTabInit(dialog, m, &box->dlg);
SetTimer(dialog, 1, 50, UpdateInfoRight);
ShowWindow(dialog, SW_SHOW);
if (!activate) SetActiveWindow(hwndPrev); // do not steal focus
}
// =====================================================================================
void info_killseeker(HWND hwnd)
// =====================================================================================
{
DLGHDR *pHdr = (DLGHDR *)GetWindowLong(hwnd, GWL_USERDATA);
if (pHdr->seeker)
{
assert(pHdr->inUse);
if (pHdr->seeker != mp)
Player_Free(pHdr->seeker);
pHdr->seeker = NULL;
}
pHdr->inUse = FALSE;
}
// =====================================================================================
static BOOL CALLBACK infoProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
// =====================================================================================
{
UNIMOD *m = NULL;
switch (uMsg)
{
case WM_INITDIALOG:
{
RECT rect, wrect;
if (GetWindowRect(mikmod.hMainWindow, &wrect) && GetWindowRect(hwndDlg, &rect))
{
wrect.left += config_info_x;
wrect.top += config_info_y;
if (wrect.left>=0 && wrect.top>=0 &&
wrect.left<GetSystemMetrics(SM_CXFULLSCREEN)-16 &&
wrect.top<GetSystemMetrics(SM_CYFULLSCREEN)-16)
MoveWindow(hwndDlg, wrect.left, wrect.top, rect.right-rect.left, rect.bottom-rect.top, FALSE);
}
}
return TRUE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDOK:
case IDCANCEL:
SendMessage(hwndDlg, WM_CLOSE, 0, 0);
break;
case IDC_TRACK:
config_track = IsDlgButtonChecked(hwndDlg, IDC_TRACK) == BST_CHECKED;
break;
}
break;
case WM_USER + 10:
case WM_CLOSE:
// save offset
{
RECT rect, wrect;
if (GetWindowRect(mikmod.hMainWindow, &wrect) && GetWindowRect(hwndDlg, &rect))
{
config_info_x = rect.left - wrect.left;
config_info_y = rect.top - wrect.top;
}
}
config_track = IsDlgButtonChecked(hwndDlg, IDC_TRACK) == BST_CHECKED;
// clean up
if (uMsg != WM_CLOSE)
break;
KillTimer(hwndDlg, 1);
infobox_delete(hwndDlg);
config_write();
break;
case WM_NOTIFY:
{
NMHDR *notice = (NMHDR*)lParam;
switch(notice->code)
{
case TCN_SELCHANGE:
OnSelChanged(hwndDlg);
break;
}
}
return TRUE;
}
return 0;
}