/******************************************************************************** Copyright (C) 2012 Hugh Bailey This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. ********************************************************************************/ #include "Main.h" #include "LogUploader.h" #include #include #include #include #include #include #include //hello, you've come into the file I hate the most. #define FREEZE_WND(hwnd) SendMessage(hwnd, WM_SETREDRAW, (WPARAM)FALSE, (LPARAM) 0); #define THAW_WND(hwnd) {SendMessage(hwnd, WM_SETREDRAW, (WPARAM)TRUE, (LPARAM) 0); RedrawWindow(hwnd, NULL, NULL, RDW_ERASE | RDW_FRAME | RDW_INVALIDATE | RDW_ALLCHILDREN);} extern WNDPROC listboxProc; extern WNDPROC listviewProc; void STDCALL SceneHotkey(DWORD hotkey, UPARAM param, bool bDown); enum { ID_LISTBOX_REMOVE = 1, ID_LISTBOX_MOVEUP, ID_LISTBOX_MOVEDOWN, ID_LISTBOX_MOVETOTOP, ID_LISTBOX_MOVETOBOTTOM, ID_LISTBOX_CENTER, ID_LISTBOX_CENTERHOR, ID_LISTBOX_CENTERVER, ID_LISTBOX_MOVELEFT, ID_LISTBOX_MOVETOP, ID_LISTBOX_MOVERIGHT, ID_LISTBOX_MOVEBOTTOM, ID_LISTBOX_FITTOSCREEN, ID_LISTBOX_RESETSIZE, ID_LISTBOX_RESETCROP, ID_LISTBOX_RENAME, ID_LISTBOX_COPY, ID_LISTBOX_HOTKEY, ID_LISTBOX_CONFIG, // Render frame related. ID_TOGGLERENDERVIEW, ID_TOGGLEPANEL, ID_TOGGLEFULLSCREEN, ID_PREVIEWSCALETOFITMODE, ID_PREVIEW1TO1MODE, ID_LISTBOX_ADD, ID_LISTBOX_GLOBALSOURCE=5000, ID_PROJECTOR=6000, ID_LISTBOX_COPYTO=7000, }; INT_PTR CALLBACK OBS::EnterSceneCollectionDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: { SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)lParam); LocalizeWindow(hwnd); String &strOut = *(String*)GetWindowLongPtr(hwnd, DWLP_USER); SetWindowText(GetDlgItem(hwnd, IDC_NAME), strOut); return true; } case WM_COMMAND: switch (LOWORD(wParam)) { case IDOK: { String str; str.SetLength((UINT)SendMessage(GetDlgItem(hwnd, IDC_NAME), WM_GETTEXTLENGTH, 0, 0)); if (!str.Length()) { OBSMessageBox(hwnd, Str("EnterName"), NULL, 0); break; } SendMessage(GetDlgItem(hwnd, IDC_NAME), WM_GETTEXT, str.Length()+1, (LPARAM)str.Array()); String &strOut = *(String*)GetWindowLongPtr(hwnd, DWLP_USER); String strSceneCollectionPath; strSceneCollectionPath << lpAppDataPath << TEXT("\\sceneCollection\\") << str << TEXT(".xconfig"); if (OSFileExists(strSceneCollectionPath)) { String strExists = Str("NameExists"); strExists.FindReplace(TEXT("$1"), str); OBSMessageBox(hwnd, strExists, NULL, 0); break; } strOut = str; } case IDCANCEL: EndDialog(hwnd, LOWORD(wParam)); break; } } return false; } INT_PTR CALLBACK OBS::EnterSourceNameDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: { SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)lParam); LocalizeWindow(hwnd); String &strOut = *(String*)GetWindowLongPtr(hwnd, DWLP_USER); SetWindowText(GetDlgItem(hwnd, IDC_NAME), strOut); //SetFocus(GetDlgItem(hwnd, IDC_NAME)); return TRUE; } case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: { String str; str.SetLength((UINT)SendMessage(GetDlgItem(hwnd, IDC_NAME), WM_GETTEXTLENGTH, 0, 0)); if(!str.Length()) { OBSMessageBox(hwnd, Str("EnterName"), NULL, 0); break; } SendMessage(GetDlgItem(hwnd, IDC_NAME), WM_GETTEXT, str.Length()+1, (LPARAM)str.Array()); String &strOut = *(String*)GetWindowLongPtr(hwnd, DWLP_USER); if(App->sceneElement) { XElement *sources = App->sceneElement->GetElement(TEXT("sources")); if(!sources) sources = App->sceneElement->CreateElement(TEXT("sources")); XElement *foundSource = sources->GetElement(str); if(foundSource != NULL && strOut != foundSource->GetName()) { String strExists = Str("NameExists"); strExists.FindReplace(TEXT("$1"), str); OBSMessageBox(hwnd, strExists, NULL, 0); break; } } strOut = str; } case IDCANCEL: EndDialog(hwnd, LOWORD(wParam)); break; } } return 0; } INT_PTR CALLBACK OBS::SceneHotkeyDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: { SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)lParam); LocalizeWindow(hwnd); SceneHotkeyInfo *hotkeyInfo = (SceneHotkeyInfo*)lParam; SendMessage(GetDlgItem(hwnd, IDC_HOTKEY), HKM_SETHOTKEY, hotkeyInfo->hotkey, 0); return TRUE; } case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_CLEAR: if(HIWORD(wParam) == BN_CLICKED) SendMessage(GetDlgItem(hwnd, IDC_HOTKEY), HKM_SETHOTKEY, 0, 0); break; case IDOK: { SceneHotkeyInfo *hotkeyInfo = (SceneHotkeyInfo*)GetWindowLongPtr(hwnd, DWLP_USER); DWORD hotkey = (DWORD)SendMessage(GetDlgItem(hwnd, IDC_HOTKEY), HKM_GETHOTKEY, 0, 0); if(hotkey == hotkeyInfo->hotkey) { EndDialog(hwnd, IDCANCEL); break; } if(hotkey) { XElement *scenes = API->GetSceneListElement(); UINT numScenes = scenes->NumElements(); for(UINT i=0; iGetElementByID(i); if(sceneElement->GetInt(TEXT("hotkey")) == hotkey) { OBSMessageBox(hwnd, Str("Scene.Hotkey.AlreadyInUse"), NULL, 0); return 0; } } } hotkeyInfo->hotkey = hotkey; } case IDCANCEL: EndDialog(hwnd, LOWORD(wParam)); break; } } return 0; } INT_PTR CALLBACK OBS::EnterSceneNameDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: { SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)lParam); LocalizeWindow(hwnd); String &strOut = *(String*)GetWindowLongPtr(hwnd, DWLP_USER); SetWindowText(GetDlgItem(hwnd, IDC_NAME), strOut); return TRUE; } case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: { String str; str.SetLength((UINT)SendMessage(GetDlgItem(hwnd, IDC_NAME), WM_GETTEXTLENGTH, 0, 0)); if(!str.Length()) { OBSMessageBox(hwnd, Str("EnterName"), NULL, 0); break; } SendMessage(GetDlgItem(hwnd, IDC_NAME), WM_GETTEXT, str.Length()+1, (LPARAM)str.Array()); String &strOut = *(String*)GetWindowLongPtr(hwnd, DWLP_USER); XElement *scenes = App->scenesConfig.GetElement(TEXT("scenes")); XElement *foundScene = scenes->GetElement(str); if(foundScene != NULL && strOut != foundScene->GetName()) { String strExists = Str("NameExists"); strExists.FindReplace(TEXT("$1"), str); OBSMessageBox(hwnd, strExists, NULL, 0); break; } strOut = str; } case IDCANCEL: EndDialog(hwnd, LOWORD(wParam)); break; } } return 0; } void OBS::GetNewSceneName(String &strScene) { XElement *scenes = App->scenesConfig.GetElement(TEXT("scenes")); if(scenes) { String strTestName = strScene; UINT num = 1; while(scenes->GetElement(strTestName) != NULL) strTestName.Clear() << strScene << FormattedString(TEXT(" %u"), ++num); strScene = strTestName; } } void OBS::GetNewSourceName(String &strSource) { XElement *sceneElement = API->GetSceneElement(); if(sceneElement) { XElement *sources = sceneElement->GetElement(TEXT("sources")); if(!sources) sources = sceneElement->CreateElement(TEXT("sources")); String strTestName = strSource; UINT num = 1; while(sources->GetElement(strTestName) != NULL) strTestName.Clear() << strSource << FormattedString(TEXT(" %u"), ++num); strSource = strTestName; } } LRESULT CALLBACK OBS::ListboxHook(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { UINT id = (UINT)GetWindowLongPtr(hwnd, GWL_ID); if(message == WM_RBUTTONDOWN) { int numItems = 0; if(id == ID_SCENES) { CallWindowProc(listboxProc, hwnd, WM_LBUTTONDOWN, wParam, lParam); numItems = (int)SendMessage(hwnd, LB_GETCOUNT, 0, 0); } else { LVHITTESTINFO htInfo; int index; // Default behaviour of left/right click is to check/uncheck, we do not want to toggle items when right clicking above checkbox. numItems = ListView_GetItemCount(hwnd); GetCursorPos(&htInfo.pt); ScreenToClient(hwnd, &htInfo.pt); index = ListView_HitTestEx(hwnd, &htInfo); if(index != -1) { // Focus our control if(GetFocus() != hwnd) SetFocus(hwnd); // Clear all selected items state and select/focus the item we've right-clicked if it wasn't previously selected. if(!(ListView_GetItemState(hwnd, index, LVIS_SELECTED) & LVIS_SELECTED)) { ListView_SetItemState(hwnd , -1 , 0, LVIS_SELECTED | LVIS_FOCUSED); ListView_SetItemState(hwnd, index, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); ListView_SetSelectionMark(hwnd, index); } } else { ListView_SetItemState(hwnd , -1 , 0, LVIS_SELECTED | LVIS_FOCUSED) CallWindowProc(listviewProc, hwnd, WM_RBUTTONDOWN, wParam, lParam); } } HMENU hMenu = CreatePopupMenu(); bool bSelected = true; if(id == ID_SCENES) { SendMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_SCENES, LBN_SELCHANGE), (LPARAM)GetDlgItem(hwndMain, ID_SCENES)); for(UINT i=0; isceneClasses.Num(); i++) { if (App->sceneClasses[i].bDeprecated) continue; String strAdd = Str("Listbox.Add"); strAdd.FindReplace(TEXT("$1"), App->sceneClasses[i].strName); AppendMenu(hMenu, MF_STRING, ID_LISTBOX_ADD+i, strAdd.Array()); } } else if(id == ID_SOURCES) { if(!App->sceneElement) return 0; bSelected = ListView_GetSelectedCount(hwnd) != 0; } App->AppendModifyListbox(hwnd, hMenu, id, numItems, bSelected); POINT p; GetCursorPos(&p); int curSel = (id== ID_SOURCES)?(ListView_GetNextItem(hwnd, -1, LVNI_SELECTED)):((int)SendMessage(hwnd, LB_GETCURSEL, 0, 0)); XElement *curSceneElement = App->sceneElement; if(id == ID_SCENES) { XElement *item = App->sceneElement; if(!item && numItems) return 0; ClassInfo *curClassInfo = NULL; if(numItems) { curClassInfo = App->GetSceneClass(item->GetString(TEXT("class"))); if(!curClassInfo) { curSceneElement->AddString(TEXT("class"), TEXT("Scene")); curClassInfo = App->GetSceneClass(item->GetString(TEXT("class"))); } if(curClassInfo && curClassInfo->configProc) { AppendMenu(hMenu, MF_SEPARATOR, 0, 0); AppendMenu(hMenu, MF_STRING, ID_LISTBOX_CONFIG, Str("Listbox.Config")); } } bool bDelete = false; int ret = (int)TrackPopupMenuEx(hMenu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_RIGHTBUTTON, p.x, p.y, hwndMain, NULL); switch(ret) { default: if(ret >= ID_LISTBOX_ADD && ret < ID_LISTBOX_COPYTO) { App->EnableSceneSwitching(false); String strName = Str("Scene"); GetNewSceneName(strName); if(OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_ENTERNAME), hwndMain, OBS::EnterSceneNameDialogProc, (LPARAM)&strName) == IDOK) { UINT classID = ret-ID_LISTBOX_ADD; ClassInfo &ci = App->sceneClasses[classID]; XElement *scenes = App->scenesConfig.GetElement(TEXT("scenes")); XElement *newSceneElement = scenes->CreateElement(strName); newSceneElement->SetString(TEXT("class"), ci.strClass); if(ci.configProc) { if(!ci.configProc(newSceneElement, true)) { scenes->RemoveElement(newSceneElement); break; } } UINT newID = (UINT)SendMessage(hwnd, LB_ADDSTRING, 0, (LPARAM)strName.Array()); PostMessage(hwnd, LB_SETCURSEL, newID, 0); PostMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_SCENES, LBN_SELCHANGE), (LPARAM)hwnd); App->ReportScenesChanged(); } App->EnableSceneSwitching(true); } if(ret >= ID_LISTBOX_COPYTO) { App->EnableSceneSwitching(false); StringList sceneCollectionList; App->GetSceneCollection(sceneCollectionList); for(UINT i = 0; i < sceneCollectionList.Num(); i++) { if(sceneCollectionList[i] == App->GetCurrentSceneCollection()) { sceneCollectionList.Remove(i); } } UINT classID = ret - ID_LISTBOX_COPYTO; String strScenesCopyToConfig; strScenesCopyToConfig = FormattedString(L"%s\\sceneCollection\\%s.xconfig", lpAppDataPath, sceneCollectionList[classID].Array()); if(!App->scenesCopyToConfig.Open(strScenesCopyToConfig)) CrashError(TEXT("Could not open '%s"), strScenesCopyToConfig.Array()); XElement *currentSceneCollection = App->scenesConfig.GetElement(TEXT("scenes")); XElement *selectedScene = currentSceneCollection->GetElementByID(curSel); XElement *copyToSceneCollection = App->scenesCopyToConfig.GetElement(TEXT("scenes")); if(!copyToSceneCollection) copyToSceneCollection = App->scenesCopyToConfig.CreateElement(TEXT("scenes")); if(copyToSceneCollection) { XElement *foundGlobalSource = copyToSceneCollection->GetElement(selectedScene->GetName()); if(foundGlobalSource != NULL && selectedScene->GetName() != foundGlobalSource->GetName()) { App->scenesCopyToConfig.Close(); App->EnableSceneSwitching(true); String strExists = Str("CopyTo.SceneNameExists"); strExists.FindReplace(TEXT("$1"), selectedScene->GetName()); OBSMessageBox(hwnd, strExists, NULL, 0); break; } } XElement *newSceneElement = copyToSceneCollection->CopyElement(selectedScene, selectedScene->GetName()); newSceneElement->SetString(TEXT("class"), selectedScene->GetString(TEXT("class"))); bool globalSourceRefCheck = false; XElement *newSceneSourcesGsRefCheck = newSceneElement->GetElement(TEXT("sources")); if(newSceneSourcesGsRefCheck) { UINT numSources = newSceneSourcesGsRefCheck->NumElements(); for(int i = int(numSources - 1); i >= 0; i--) { XElement *sourceElement = newSceneSourcesGsRefCheck->GetElementByID(i); String sourceClassName = sourceElement->GetString(TEXT("class")); if(sourceClassName == "GlobalSource") { globalSourceRefCheck = true; } } } if(globalSourceRefCheck) { if(OBSMessageBox(hwndMain, Str("CopyTo.CopyGlobalSourcesReferences"), Str("CopyTo.CopyGlobalSourcesReferences.Title"), MB_YESNO) == IDYES) { XElement *newSceneSources = newSceneElement->GetElement(TEXT("sources")); if(newSceneSources) { UINT numSources = newSceneSources->NumElements(); for(int i = int(numSources - 1); i >= 0; i--) { XElement *sourceElement = newSceneSources->GetElementByID(i); String sourceClassName = sourceElement->GetString(TEXT("class")); if(sourceClassName == "GlobalSource") { XElement *data = sourceElement->GetElement(TEXT("data")); if(data) { CTSTR lpName = data->GetString(TEXT("name")); XElement *globalSourceName = App->GetGlobalSourceElement(lpName); if(globalSourceName) { XElement *importGlobalSources = App->scenesCopyToConfig.GetElement(TEXT("global sources")); if(!importGlobalSources) importGlobalSources = App->scenesCopyToConfig.CreateElement(TEXT("global sources")); if(importGlobalSources) { XElement *foundGlobalSource = importGlobalSources->GetElement(globalSourceName->GetName()); if(foundGlobalSource != NULL && globalSourceName->GetName() != foundGlobalSource->GetName()) { String strGsExists = Str("CopyTo.GlobalSourcesExists"); strGsExists.FindReplace(TEXT("$1"), globalSourceName->GetName()); OBSMessageBox(hwnd, strGsExists, NULL, 0); } else { XElement *newGlobalSources = importGlobalSources->CopyElement(globalSourceName, globalSourceName->GetName()); newGlobalSources->SetString(TEXT("class"), globalSourceName->GetString(TEXT("class"))); } } } } } } } } else { XElement *newSceneSources = newSceneElement->GetElement(TEXT("sources")); if(newSceneSources) { UINT numSources = newSceneSources->NumElements(); for(int i = int(numSources - 1); i >= 0; i--) { XElement *sourceElement = newSceneSources->GetElementByID(i); String sourceClassName = sourceElement->GetString(TEXT("class")); if(sourceClassName == "GlobalSource") { newSceneSources->RemoveElement(sourceElement); } } } } } String strCopied = Str("CopyTo.Success.Text"); strCopied.FindReplace(TEXT("$1"), selectedScene->GetName()); OBSMessageBox(hwnd, strCopied, Str("CopyTo.Success.Title"), 0); App->EnableSceneSwitching(true); App->scenesCopyToConfig.Close(true); } break; case ID_LISTBOX_REMOVE: App->EnableSceneSwitching(false); if(OBSMessageBox(hwndMain, Str("DeleteConfirm"), Str("DeleteConfirm.Title"), MB_YESNO) == IDYES) { DWORD hotkey = item->GetInt(TEXT("hotkey")); if(hotkey) App->RemoveSceneHotkey(hotkey); SendMessage(hwnd, LB_DELETESTRING, curSel, 0); if(--numItems) { if(curSel == numItems) curSel--; } else curSel = LB_ERR; App->ReportScenesChanged(); bDelete = true; } App->EnableSceneSwitching(true); break; case ID_LISTBOX_RENAME: { App->EnableSceneSwitching(false); String strName = item->GetName(); if(OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_ENTERNAME), hwndMain, OBS::EnterSceneNameDialogProc, (LPARAM)&strName) == IDOK) { SendMessage(hwnd, LB_DELETESTRING, curSel, 0); SendMessage(hwnd, LB_INSERTSTRING, curSel, (LPARAM)strName.Array()); SendMessage(hwnd, LB_SETCURSEL, curSel, 0); item->SetName(strName); App->ReportScenesChanged(); } App->EnableSceneSwitching(true); break; } case ID_LISTBOX_COPY: { App->EnableSceneSwitching(false); String strName = Str("Scene"); GetNewSceneName(strName); if(OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_ENTERNAME), hwndMain, OBS::EnterSceneNameDialogProc, (LPARAM)&strName) == IDOK) { UINT classID = 0; // ID_LISTBOX_ADD - ID_LISTBOX_ADD ClassInfo &ci = App->sceneClasses[classID]; XElement *scenes = App->scenesConfig.GetElement(TEXT("scenes")); XElement *newSceneElement = scenes->CopyElement(item, strName); newSceneElement->SetString(TEXT("class"), ci.strClass); newSceneElement->SetInt(TEXT("hotkey"), 0); if(ci.configProc) { if(!ci.configProc(newSceneElement, true)) { scenes->RemoveElement(newSceneElement); break; } } UINT newID = (UINT)SendMessage(hwnd, LB_ADDSTRING, 0, (LPARAM)strName.Array()); PostMessage(hwnd, LB_SETCURSEL, newID, 0); PostMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_SCENES, LBN_SELCHANGE), (LPARAM)hwnd); App->ReportScenesChanged(); } App->EnableSceneSwitching(true); break; } case ID_LISTBOX_CONFIG: App->EnableSceneSwitching(false); if(curClassInfo && curClassInfo->configProc(item, false)) { if(App->bRunning) App->scene->UpdateSettings(); } App->EnableSceneSwitching(true); break; case ID_LISTBOX_HOTKEY: { App->EnableSceneSwitching(false); DWORD prevHotkey = item->GetInt(TEXT("hotkey")); SceneHotkeyInfo hotkeyInfo; hotkeyInfo.hotkey = prevHotkey; hotkeyInfo.scene = item; if(OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_SCENEHOTKEY), hwndMain, OBS::SceneHotkeyDialogProc, (LPARAM)&hotkeyInfo) == IDOK) { if(hotkeyInfo.hotkey) hotkeyInfo.hotkeyID = API->CreateHotkey(hotkeyInfo.hotkey, SceneHotkey, 0); item->SetInt(TEXT("hotkey"), hotkeyInfo.hotkey); if(prevHotkey) App->RemoveSceneHotkey(prevHotkey); if(hotkeyInfo.hotkeyID) App->sceneHotkeys << hotkeyInfo; } App->EnableSceneSwitching(true); break; } case ID_LISTBOX_MOVEUP: if(curSel > 0) { SendMessage(hwnd, LB_DELETESTRING, curSel, 0); SendMessage(hwnd, LB_INSERTSTRING, curSel-1, (LPARAM)item->GetName()); SendMessage(hwnd, LB_SETCURSEL, curSel-1, 0); curSel--; item->MoveUp(); App->ReportScenesChanged(); } break; case ID_LISTBOX_MOVEDOWN: if(curSel != (numItems-1)) { SendMessage(hwnd, LB_DELETESTRING, curSel, 0); SendMessage(hwnd, LB_INSERTSTRING, curSel+1, (LPARAM)item->GetName()); SendMessage(hwnd, LB_SETCURSEL, curSel+1, 0); curSel++; item->MoveDown(); App->ReportScenesChanged(); } break; case ID_LISTBOX_MOVETOTOP: if(curSel != 0) { SendMessage(hwnd, LB_DELETESTRING, curSel, 0); SendMessage(hwnd, LB_INSERTSTRING, 0, (LPARAM)item->GetName()); SendMessage(hwnd, LB_SETCURSEL, 0, 0); curSel = 0; item->MoveToTop(); App->ReportScenesChanged(); } break; case ID_LISTBOX_MOVETOBOTTOM: if(curSel != numItems-1) { SendMessage(hwnd, LB_DELETESTRING, curSel, 0); SendMessage(hwnd, LB_INSERTSTRING, numItems-1, (LPARAM)item->GetName()); SendMessage(hwnd, LB_SETCURSEL, numItems-1, 0); curSel = numItems-1; item->MoveToBottom(); App->ReportScenesChanged(); } break; } if(curSel != LB_ERR) { SendMessage(hwnd, LB_SETCURSEL, curSel, 0); SendMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_SCENES, LBN_SELCHANGE), (LPARAM)GetDlgItem(hwndMain, ID_SCENES)); if(bDelete) item->GetParent()->RemoveElement(item); } else if(bDelete) { if(App->bRunning) { OSEnterMutex(App->hSceneMutex); delete App->scene; App->scene = NULL; OSLeaveMutex(App->hSceneMutex); } App->bChangingSources = true; ListView_DeleteAllItems(GetDlgItem(hwndMain, ID_SOURCES)); App->bChangingSources = false; item->GetParent()->RemoveElement(item); App->sceneElement = NULL; } } else if(id == ID_SOURCES) { if(!App->sceneElement && numItems) return 0; List selectedSceneItems; if(App->scene) App->scene->GetSelectedItems(selectedSceneItems); if(selectedSceneItems.Num() < 1) nop(); int ret = (int)TrackPopupMenuEx(hMenu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_RIGHTBUTTON, p.x, p.y, hwndMain, NULL); App->TrackModifyListbox(hwnd, ret); } DestroyMenu(hMenu); return 0; } if(id == ID_SOURCES) { return CallWindowProc(listviewProc, hwnd, message, wParam, lParam); } else { return CallWindowProc(listboxProc, hwnd, message, wParam, lParam); } } void OBS::TrackModifyListbox(HWND hwnd, int ret) { HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES); UINT numSelected = (ListView_GetSelectedCount(hwndSources)); XElement *selectedElement = NULL; ClassInfo *curClassInfo = NULL; if(numSelected == 1) { UINT selectedID = ListView_GetNextItem(hwndSources, -1, LVNI_SELECTED); XElement *sourcesElement = App->sceneElement->GetElement(TEXT("sources")); selectedElement = sourcesElement->GetElementByID(selectedID); curClassInfo = App->GetImageSourceClass(selectedElement->GetString(TEXT("class"))); } switch(ret) { // General render frame stuff above here case ID_TOGGLERENDERVIEW: App->bRenderViewEnabled = !App->bRenderViewEnabled; App->bForceRenderViewErase = !App->bRenderViewEnabled; App->UpdateRenderViewMessage(); break; case ID_TOGGLEPANEL: if (App->bFullscreenMode) App->bPanelVisibleFullscreen = !App->bPanelVisibleFullscreen; else App->bPanelVisibleWindowed = !App->bPanelVisibleWindowed; App->bPanelVisible = App->bFullscreenMode ? App->bPanelVisibleFullscreen : App->bPanelVisibleWindowed; App->bPanelVisibleProcessed = false; App->ResizeWindow(true); break; case ID_TOGGLEFULLSCREEN: App->SetFullscreenMode(!App->bFullscreenMode); break; case ID_PREVIEWSCALETOFITMODE: App->renderFrameIn1To1Mode = false; App->ResizeRenderFrame(true); break; case ID_PREVIEW1TO1MODE: App->renderFrameIn1To1Mode = true; App->ResizeRenderFrame(true); break; // Sources below here default: if (ret >= ID_PROJECTOR) { UINT monitorID = ret-ID_PROJECTOR; if (monitorID == 0) App->bPleaseDisableProjector = true; else EnableProjector(monitorID-1); } else if(ret >= ID_LISTBOX_ADD) { App->EnableSceneSwitching(false); ClassInfo *ci; if(ret >= ID_LISTBOX_GLOBALSOURCE) ci = App->GetImageSourceClass(TEXT("GlobalSource")); else { UINT classID = ret-ID_LISTBOX_ADD; ci = App->imageSourceClasses+classID; } String strName; if(ret >= ID_LISTBOX_GLOBALSOURCE) { List sourceNames; App->GetGlobalSourceNames(sourceNames); strName = sourceNames[ret-ID_LISTBOX_GLOBALSOURCE]; } else strName = ci->strName; GetNewSourceName(strName); if(OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_ENTERNAME), hwndMain, OBS::EnterSourceNameDialogProc, (LPARAM)&strName) == IDOK) { XElement *curSceneElement = App->sceneElement; XElement *sources = curSceneElement->GetElement(TEXT("sources")); if(!sources) sources = curSceneElement->CreateElement(TEXT("sources")); XElement *newSourceElement = sources->InsertElement(0, strName); newSourceElement->SetInt(TEXT("render"), 1); if(ret >= ID_LISTBOX_GLOBALSOURCE) { newSourceElement->SetString(TEXT("class"), TEXT("GlobalSource")); List sourceNames; App->GetGlobalSourceNames(sourceNames); CTSTR lpName = sourceNames[ret-ID_LISTBOX_GLOBALSOURCE]; XElement *data = newSourceElement->CreateElement(TEXT("data")); data->SetString(TEXT("name"), lpName); XElement *globalElement = App->GetGlobalSourceElement(lpName); if(globalElement) { newSourceElement->SetInt(TEXT("cx"), globalElement->GetInt(TEXT("cx"), 100)); newSourceElement->SetInt(TEXT("cy"), globalElement->GetInt(TEXT("cy"), 100)); } } else { newSourceElement->SetString(TEXT("class"), ci->strClass); if(ci->configProc) { if(!ci->configProc(newSourceElement, true)) { sources->RemoveElement(newSourceElement); App->EnableSceneSwitching(true); break; } } } if(App->sceneElement == curSceneElement) { if(App->bRunning) { App->EnterSceneMutex(); App->scene->InsertImageSource(0, newSourceElement); App->LeaveSceneMutex(); } UINT numSources = sources->NumElements(); // clear selection/focus for all items before adding the new item ListView_SetItemState(hwndSources , -1 , 0, LVIS_SELECTED | LVIS_FOCUSED); ListView_SetItemCount(hwndSources, numSources); App->bChangingSources = true; App->InsertSourceItem(0, (LPWSTR)strName.Array(), true); App->bChangingSources = false; SetFocus(hwndSources); // make sure the added item is visible/selected/focused and selection mark moved to it. ListView_EnsureVisible(hwndSources, 0, false); ListView_SetItemState(hwndSources, 0, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED); ListView_SetSelectionMark(hwndSources, 0); App->ReportSourcesAddedOrRemoved(); } } App->EnableSceneSwitching(true); } break; case ID_LISTBOX_REMOVE: App->DeleteItems(); break; case ID_LISTBOX_RENAME: { if (!selectedElement) break; App->EnableSceneSwitching(false); String strName = selectedElement->GetName(); TSTR oldStrName = sdup(strName.Array()); if(OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_ENTERNAME), hwndMain, OBS::EnterSourceNameDialogProc, (LPARAM)&strName) == IDOK) { int curSel = (int)SendMessage(hwndSources, LB_GETCURSEL, 0, 0); ListView_SetItemText(hwndSources, curSel, 0, strName.Array()); selectedElement->SetName(strName); App->ReportSourceChanged(oldStrName, selectedElement); Free((void*)oldStrName); ListView_SetColumnWidth(hwndSources, 0, LVSCW_AUTOSIZE_USEHEADER); ListView_SetColumnWidth(hwndSources, 1, LVSCW_AUTOSIZE_USEHEADER); } App->EnableSceneSwitching(true); break; } case ID_LISTBOX_CONFIG: { App->EnableSceneSwitching(false); List selectedSceneItems; if(App->scene) App->scene->GetSelectedItems(selectedSceneItems); ImageSource *source = NULL; Vect2 multiple; if(App->bRunning && selectedSceneItems.Num()) { source = selectedSceneItems[0]->GetSource(); if(source) { Vect2 curSize = Vect2(selectedElement->GetFloat(TEXT("cx"), 32.0f), selectedElement->GetFloat(TEXT("cy"), 32.0f)); Vect2 baseSize = source->GetSize(); multiple = curSize/baseSize; } } if(curClassInfo && curClassInfo->configProc && curClassInfo->configProc(selectedElement, false)) { if(App->bRunning && selectedSceneItems.Num()) { App->EnterSceneMutex(); if(source) { Vect2 newSize = Vect2(selectedElement->GetFloat(TEXT("cx"), 32.0f), selectedElement->GetFloat(TEXT("cy"), 32.0f)); newSize *= multiple; selectedElement->SetFloat(TEXT("cx"), newSize.x); selectedElement->SetFloat(TEXT("cy"), newSize.y); selectedSceneItems[0]->GetSource()->UpdateSettings(); } selectedSceneItems[0]->Update(); App->LeaveSceneMutex(); } } App->EnableSceneSwitching(true); } break; case ID_LISTBOX_MOVEUP: App->MoveSourcesUp(); break; case ID_LISTBOX_MOVEDOWN: App->MoveSourcesDown(); break; case ID_LISTBOX_MOVETOTOP: App->MoveSourcesToTop(); break; case ID_LISTBOX_MOVETOBOTTOM: App->MoveSourcesToBottom(); break; case ID_LISTBOX_CENTER: App->CenterItems(true, true); break; case ID_LISTBOX_CENTERHOR: App->CenterItems(true, false); break; case ID_LISTBOX_CENTERVER: App->CenterItems(false, true); break; case ID_LISTBOX_MOVELEFT: App->MoveItemsToEdge(-1, 0); break; case ID_LISTBOX_MOVETOP: App->MoveItemsToEdge(0, -1); break; case ID_LISTBOX_MOVERIGHT: App->MoveItemsToEdge(1, 0); break; case ID_LISTBOX_MOVEBOTTOM: App->MoveItemsToEdge(0, 1); break; case ID_LISTBOX_FITTOSCREEN: App->FitItemsToScreen(); break; case ID_LISTBOX_RESETSIZE: App->ResetItemSizes(); break; case ID_LISTBOX_RESETCROP: App->ResetItemCrops(); break; } } void OBS::AppendModifyListbox(HWND hwnd, HMENU hMenu, int id, int numItems, bool bSelected) { if(GetMenuItemCount(hMenu) > 0 && id == ID_SOURCES) AppendMenu(hMenu, MF_SEPARATOR, 0, 0); if(id == ID_SOURCES) { HMENU hmenuAdd = CreatePopupMenu(); for(UINT i=0; iimageSourceClasses.Num(); i++) { if (App->imageSourceClasses[i].bDeprecated) continue; String strAdd = App->imageSourceClasses[i].strName; if(App->imageSourceClasses[i].strClass == TEXT("GlobalSource")) { List sourceNames; App->GetGlobalSourceNames(sourceNames); if(!sourceNames.Num()) continue; HMENU hmenuGlobals = CreatePopupMenu(); for(UINT j=0; jGetCurrentSceneCollection()) { sceneCollectionList.Remove(k); } } for(UINT i = 0; i < sceneCollectionList.Num(); i++) { AppendMenu(hMenuCopyTo, MF_STRING, ID_LISTBOX_COPYTO + i, sceneCollectionList[i]); } AppendMenu(hMenu, MF_STRING, ID_LISTBOX_COPY, strCopy); AppendMenu(hMenu, MF_STRING|MF_POPUP,(UINT_PTR)hMenuCopyTo, strCopyTo); AppendMenu(hMenu, MF_SEPARATOR, 0, 0); AppendMenu(hMenu, MF_STRING, ID_LISTBOX_HOTKEY, Str("Listbox.SetHotkey")); } if(id == ID_SOURCES) { UINT numSelected = (ListView_GetSelectedCount(hwnd)); XElement *selectedElement = NULL; ClassInfo *curClassInfo = NULL; if(numSelected == 1) { UINT selectedID = ListView_GetNextItem(hwnd, -1, LVNI_SELECTED); XElement *sourcesElement = App->sceneElement->GetElement(TEXT("sources")); selectedElement = sourcesElement->GetElementByID(selectedID); curClassInfo = App->GetImageSourceClass(selectedElement->GetString(TEXT("class"))); if(curClassInfo && curClassInfo->configProc) { AppendMenu(hMenu, MF_SEPARATOR, 0, 0); AppendMenu(hMenu, MF_STRING, ID_LISTBOX_CONFIG, Str("Listbox.Config")); } } } } } //---------------------------- void OBS::DeleteItems() { HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES); int numItems = ListView_GetItemCount(hwndSources); List selectedSceneItems; if(App->scene) App->scene->GetSelectedItems(selectedSceneItems); List selectedIDs; //get selected items int iPos = ListView_GetNextItem(hwndSources, -1, LVNI_SELECTED); while (iPos != -1) { selectedIDs.Add((UINT) iPos); iPos = ListView_GetNextItem(hwndSources, iPos, LVNI_SELECTED); } XElement *sourcesElement = NULL; List selectedElements; if(numItems) { sourcesElement = App->sceneElement->GetElement(TEXT("sources")); for(UINT i=0; iGetElementByID(selectedIDs[i]); } if(selectedIDs.Num()) { if(OBSMessageBox(hwndMain, Str("DeleteConfirm"), Str("DeleteConfirm.Title"), MB_YESNO) == IDYES) { if(selectedSceneItems.Num()) { App->EnterSceneMutex(); for(UINT i=0; iGetElement(); String className = source->GetString(TEXT("class")); if(className == "GlobalSource") { String globalSourceName = source->GetElement(TEXT("data"))->GetString(TEXT("name")); if (App->GetGlobalSource(globalSourceName) != NULL) App->GetGlobalSource(globalSourceName)->GlobalSourceLeaveScene(); } App->scene->RemoveImageSource(item); } } else { for(UINT i=0; iRemoveElement(selectedElements[i]); } while(selectedIDs.Num()) { UINT id = selectedIDs[0]; selectedIDs.Remove(0); for(UINT i=0; i id) selectedIDs[i]--; } App->bChangingSources = true; ListView_DeleteItem(hwndSources, id); ListView_SetColumnWidth(hwndSources, 0, LVSCW_AUTOSIZE_USEHEADER); ListView_SetColumnWidth(hwndSources, 1, LVSCW_AUTOSIZE_USEHEADER); App->bChangingSources = false; } if(selectedSceneItems.Num()) App->LeaveSceneMutex(); ReportSourcesAddedOrRemoved(); } } } void OBS::SetSourceOrder(StringList &sourceNames) { HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES); int numItems = ListView_GetItemCount(hwndSources); if(numItems == 0) return; XElement* sourcesElement = App->sceneElement->GetElement(TEXT("sources")); FREEZE_WND(hwndSources); for(UINT i=0; iNumElements(); j++) { XElement* source = sourcesElement->GetElementByID(j); if(scmp(source->GetName(), sourceNames[i].Array()) == 0) { id = j; break; } } if(id >= 0) { if(App->scene) { App->scene->GetSceneItem(id)->MoveToBottom(); } else { sourcesElement->GetElementByID(id)->MoveToBottom(); } String strName = GetLVText(hwndSources, id); bool checkState = ListView_GetCheckState(hwndSources, id) > 0; bChangingSources = true; ListView_DeleteItem(hwndSources, id); InsertSourceItem(numItems-1, (LPWSTR)strName.Array(), checkState); bChangingSources = false; } } THAW_WND(hwndSources); ReportSourceOrderChanged(); } void OBS::MoveSourcesUp() { HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES); int numItems = ListView_GetItemCount(hwndSources); UINT focusedItem = -1, selectionMark; List selectedSceneItems; if(App->scene) App->scene->GetSelectedItems(selectedSceneItems); List selectedIDs; selectionMark = ListView_GetSelectionMark(hwndSources); //get selected items int iPos = ListView_GetNextItem(hwndSources, -1, LVNI_SELECTED); while (iPos != -1) { selectedIDs.Add((UINT) iPos); if(ListView_GetItemState(hwndSources, iPos, LVIS_FOCUSED) & LVIS_FOCUSED) focusedItem = iPos; iPos = ListView_GetNextItem(hwndSources, iPos, LVNI_SELECTED); } if (!selectedSceneItems.Num() && !selectedIDs.Num()) return; XElement *sourcesElement = NULL; List selectedElements; if(numItems) { sourcesElement = App->sceneElement->GetElement(TEXT("sources")); for(UINT i=0; iGetElementByID(selectedIDs[i]); } UINT stateFlags; FREEZE_WND(hwndSources); for(UINT i=0; i 0) || (i != 0 && selectedIDs[i-1] != selectedIDs[i]-1) ) { if(App->scene) selectedSceneItems[i]->MoveUp(); else selectedElements[i]->MoveUp(); String strName = GetLVText(hwndSources, selectedIDs[i]); bool checkState = ListView_GetCheckState(hwndSources, selectedIDs[i]) > 0; bChangingSources = true; ListView_DeleteItem(hwndSources, selectedIDs[i]); InsertSourceItem(--selectedIDs[i], (LPWSTR)strName.Array(), checkState); if(focusedItem == selectedIDs[i]+1) stateFlags = LVIS_SELECTED | LVIS_FOCUSED; else stateFlags = LVIS_SELECTED; if(selectionMark == selectedIDs[i]+1) ListView_SetSelectionMark(hwndSources, selectedIDs[i]); ListView_SetItemState(hwndSources, selectedIDs[i], stateFlags, stateFlags); bChangingSources = false; } } THAW_WND(hwndSources); ReportSourceOrderChanged(); } void OBS::MoveSourcesDown() { HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES); UINT numSelected = ListView_GetSelectedCount(hwndSources); int numItems = ListView_GetItemCount(hwndSources); int focusedItem = -1, selectionMark; List selectedSceneItems; if(App->scene) App->scene->GetSelectedItems(selectedSceneItems); List selectedIDs; selectionMark = ListView_GetSelectionMark(hwndSources); //get selected items int iPos = ListView_GetNextItem(hwndSources, -1, LVNI_SELECTED); while (iPos != -1) { selectedIDs.Add((UINT) iPos); if(ListView_GetItemState(hwndSources, iPos, LVIS_FOCUSED) & LVIS_FOCUSED) focusedItem = iPos; iPos = ListView_GetNextItem(hwndSources, iPos, LVNI_SELECTED); } if (!selectedSceneItems.Num() && !selectedIDs.Num()) return; XElement *sourcesElement = NULL; List selectedElements; if(numItems) { sourcesElement = App->sceneElement->GetElement(TEXT("sources")); for(UINT i=0; iGetElementByID(selectedIDs[i]); } UINT lastItem = (UINT)ListView_GetItemCount(hwndSources)-1; UINT lastSelectedID = numSelected-1; UINT stateFlags; FREEZE_WND(hwndSources); for(int i=(int)lastSelectedID; i>=0; i--) { if( (i == lastSelectedID && selectedIDs[i] < lastItem) || (i != lastSelectedID && selectedIDs[i+1] != selectedIDs[i]+1) ) { if(App->scene) selectedSceneItems[i]->MoveDown(); else selectedElements[i]->MoveDown(); String strName = GetLVText(hwndSources, selectedIDs[i]); bool checkState = ListView_GetCheckState(hwndSources, selectedIDs[i]) > 0; bChangingSources = true; ListView_DeleteItem(hwndSources, selectedIDs[i]); InsertSourceItem(++selectedIDs[i], (LPWSTR)strName.Array(), checkState); if(focusedItem == selectedIDs[i]-1) stateFlags = LVIS_SELECTED | LVIS_FOCUSED; else stateFlags = LVIS_SELECTED; if(selectionMark == selectedIDs[i]-1) ListView_SetSelectionMark(hwndSources, selectedIDs[i]); ListView_SetItemState(hwndSources, selectedIDs[i], stateFlags, stateFlags); bChangingSources = false; } } THAW_WND(hwndSources); ReportSourceOrderChanged(); } void OBS::MoveSourcesToTop() { HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES); int numItems = ListView_GetItemCount(hwndSources); UINT focusedItem = -1, selectionMark; List selectedSceneItems; if(App->scene) App->scene->GetSelectedItems(selectedSceneItems); List selectedIDs; selectionMark = ListView_GetSelectionMark(hwndSources); //get selected items int iPos = ListView_GetNextItem(hwndSources, -1, LVNI_SELECTED); while (iPos != -1) { selectedIDs.Add((UINT) iPos); if(ListView_GetItemState(hwndSources, iPos, LVIS_FOCUSED) & LVIS_FOCUSED) focusedItem = iPos; iPos = ListView_GetNextItem(hwndSources, iPos, LVNI_SELECTED); } if (!selectedSceneItems.Num() && !selectedIDs.Num()) return; XElement *sourcesElement = NULL; List selectedElements; if(numItems) { sourcesElement = App->sceneElement->GetElement(TEXT("sources")); for(UINT i=0; iGetElementByID(selectedIDs[i]); } if(App->scene) { for(int i=(int)selectedSceneItems.Num()-1; i>=0; i--) selectedSceneItems[i]->MoveToTop(); } else { for(int i=(int)selectedElements.Num()-1; i>=0; i--) selectedElements[i]->MoveToTop(); } UINT stateFlags; FREEZE_WND(hwndSources); for(UINT i=0; i 0; bChangingSources = true; ListView_DeleteItem(hwndSources, selectedIDs[i]); InsertSourceItem(i, (LPWSTR)strName.Array(), checkState); if(focusedItem == selectedIDs[i]) stateFlags = LVIS_SELECTED | LVIS_FOCUSED; else stateFlags = LVIS_SELECTED; if(selectionMark == selectedIDs[i]) ListView_SetSelectionMark(hwndSources, i); ListView_SetItemState(hwndSources, i, stateFlags, stateFlags); bChangingSources = false; } } THAW_WND(hwndSources); ReportSourceOrderChanged(); } void OBS::MoveSourcesToBottom() { HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES); int numItems = ListView_GetItemCount(hwndSources); UINT focusedItem = -1, selectionMark; List selectedSceneItems; if(App->scene) App->scene->GetSelectedItems(selectedSceneItems); List selectedIDs; selectionMark = ListView_GetSelectionMark(hwndSources); //get selected items int iPos = ListView_GetNextItem(hwndSources, -1, LVNI_SELECTED); while (iPos != -1) { selectedIDs.Add((UINT) iPos); if(ListView_GetItemState(hwndSources, iPos, LVIS_FOCUSED) & LVIS_FOCUSED) focusedItem = iPos; iPos = ListView_GetNextItem(hwndSources, iPos, LVNI_SELECTED); } if (!selectedSceneItems.Num() && !selectedIDs.Num()) return; XElement *sourcesElement = NULL; List selectedElements; if(numItems) { sourcesElement = App->sceneElement->GetElement(TEXT("sources")); for(UINT i=0; iGetElementByID(selectedIDs[i]); } if(App->scene) { for(UINT i=0; iMoveToBottom(); } else { for(UINT i=0; iMoveToBottom(); } UINT curID = ListView_GetItemCount(hwndSources)-1; UINT stateFlags; FREEZE_WND(hwndSources); for(int i=int(selectedIDs.Num()-1); i>=0; i--) { if(selectedIDs[i] != curID) { String strName = GetLVText(hwndSources, selectedIDs[i]); bool checkState = ListView_GetCheckState(hwndSources, selectedIDs[i]) > 0; bChangingSources = true; ListView_DeleteItem(hwndSources, selectedIDs[i]); InsertSourceItem(curID, (LPWSTR)strName.Array(), checkState); if(focusedItem == selectedIDs[i]) stateFlags = LVIS_SELECTED | LVIS_FOCUSED; else stateFlags = LVIS_SELECTED; if(selectionMark == selectedIDs[i]) ListView_SetSelectionMark(hwndSources, curID); ListView_SetItemState(hwndSources, curID, stateFlags, stateFlags); bChangingSources = false; } curID--; } THAW_WND(hwndSources); ReportSourceOrderChanged(); } void OBS::CenterItems(bool horizontal, bool vertical) { if(App->bRunning) { List selectedItems; App->scene->GetSelectedItems(selectedItems); Vect2 baseSize = App->GetBaseSize(); for(UINT i=0; ipos.x = (baseSize.x*0.5f)-((item->size.x + item->GetCrop().x - item->GetCrop().w)*0.5f); if (vertical) item->pos.y = (baseSize.y*0.5f)-((item->size.y + item->GetCrop().y - item->GetCrop().z)*0.5f); XElement *itemElement = item->GetElement(); if (horizontal) itemElement->SetInt(TEXT("x"), int(item->pos.x)); if (vertical) itemElement->SetInt(TEXT("y"), int(item->pos.y)); } } } void OBS::MoveItemsToEdge(int horizontal, int vertical) { if(App->bRunning) { List selectedItems; App->scene->GetSelectedItems(selectedItems); Vect2 baseSize = App->GetBaseSize(); for(UINT i=0; ipos.x = baseSize.x - item->size.x + item->GetCrop().w; if (horizontal == -1) item->pos.x = -item->GetCrop().x; if (vertical == 1) item->pos.y = baseSize.y - item->size.y + item->GetCrop().z; if (vertical == -1) item->pos.y = -item->GetCrop().y; XElement *itemElement = item->GetElement(); if (horizontal) itemElement->SetInt(TEXT("x"), int(item->pos.x)); if (vertical) itemElement->SetInt(TEXT("y"), int(item->pos.y)); } } } void OBS::MoveItemsByPixels(int dx, int dy) { if(App->bRunning) { List selectedItems; App->scene->GetSelectedItems(selectedItems); Vect2 baseSize = App->GetBaseSize(); Vect2 renderSize = App->GetRenderFrameSize(); for(UINT i=0; ipos.x += dx; item->pos.y += dy; if(App->bMouseMoved) { App->startMousePos.x -= dx * renderSize.x / baseSize.x; App->startMousePos.y -= dy * renderSize.y / baseSize.y; } XElement *itemElement = item->GetElement(); itemElement->SetInt(TEXT("x"), int(item->pos.x)); itemElement->SetInt(TEXT("y"), int(item->pos.y)); } } } extern "C" double round(double val); void OBS::FitItemsToScreen() { if(App->bRunning) { List selectedItems; App->scene->GetSelectedItems(selectedItems); Vect2 baseSize = App->GetBaseSize(); double baseAspect = double(baseSize.x)/double(baseSize.y); for(UINT i=0; ipos = (baseSize*0.5f)-(item->size*0.5f); if(item->source) { Vect2 itemSize = item->source->GetSize(); itemSize.x -= (item->crop.x + item->crop.w); itemSize.y -= (item->crop.y + item->crop.z); Vect2 size = baseSize; double sourceAspect = double(itemSize.x)/double(itemSize.y); if(!CloseDouble(baseAspect, sourceAspect)) { if(baseAspect < sourceAspect) size.y = float(double(size.x) / sourceAspect); else size.x = float(double(size.y) * sourceAspect); size.x = (float)round(size.x); size.y = (float)round(size.y); } Vect2 scale = itemSize / size; size.x += (item->crop.x + item->crop.w) / scale.x; size.y += (item->crop.y + item->crop.z) / scale.y; item->size = size; Vect2 pos; pos.x = (baseSize.x*0.5f)-((item->size.x + item->GetCrop().x - item->GetCrop().w)*0.5f); pos.y = (baseSize.y*0.5f)-((item->size.y + item->GetCrop().y - item->GetCrop().z)*0.5f); pos.x = (float)round(pos.x); pos.y = (float)round(pos.y); item->pos = pos; XElement *itemElement = item->GetElement(); itemElement->SetInt(TEXT("x"), int(pos.x)); itemElement->SetInt(TEXT("y"), int(pos.y)); itemElement->SetInt(TEXT("cx"), int(size.x)); itemElement->SetInt(TEXT("cy"), int(size.y)); } } } } void OBS::ResetItemSizes() { if(App->bRunning) { List selectedItems; App->scene->GetSelectedItems(selectedItems); for(UINT i=0; isource) { item->size = item->source->GetSize(); XElement *itemElement = item->GetElement(); itemElement->SetInt(TEXT("cx"), int(item->size.x)); itemElement->SetInt(TEXT("cy"), int(item->size.y)); } } } } void OBS::ResetItemCrops() { if(App->bRunning) { List selectedItems; App->scene->GetSelectedItems(selectedItems); for(UINT i=0; isource) { item->crop = Vect4(0, 0, 0, 0); XElement *itemElement = item->GetElement(); itemElement->SetFloat(TEXT("crop.left"), item->crop.x); itemElement->SetFloat(TEXT("crop.top"), item->crop.y); itemElement->SetFloat(TEXT("crop.right"), item->crop.w); itemElement->SetFloat(TEXT("crop.bottom"), item->crop.z); } } } } //---------------------------- INT_PTR CALLBACK OBS::EnterGlobalSourceNameDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: { SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)lParam); LocalizeWindow(hwnd); String &strOut = *(String*)lParam; SetWindowText(GetDlgItem(hwnd, IDC_NAME), strOut); } return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: { String str; str.SetLength((UINT)SendMessage(GetDlgItem(hwnd, IDC_NAME), WM_GETTEXTLENGTH, 0, 0)); if(!str.Length()) { OBSMessageBox(hwnd, Str("EnterName"), NULL, 0); break; } SendMessage(GetDlgItem(hwnd, IDC_NAME), WM_GETTEXT, str.Length()+1, (LPARAM)str.Array()); String &strOut = *(String*)GetWindowLongPtr(hwnd, DWLP_USER); XElement *globals = App->scenesConfig.GetElement(TEXT("global sources")); if(globals) { XElement *foundSource = globals->GetElement(str); if(foundSource != NULL && strOut != foundSource->GetName()) { String strExists = Str("NameExists"); strExists.FindReplace(TEXT("$1"), str); OBSMessageBox(hwnd, strExists, NULL, 0); break; } } strOut = str; } case IDCANCEL: EndDialog(hwnd, LOWORD(wParam)); break; } } return 0; } INT_PTR CALLBACK OBS::GlobalSourcesProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: { LocalizeWindow(hwnd); HWND hwndSources = GetDlgItem(hwnd, IDC_SOURCES); XElement *globals = App->scenesConfig.GetElement(TEXT("global sources")); if(globals) { UINT numGlobals = globals->NumElements(); for(UINT i=0; iGetElementByID(i); SendMessage(hwndSources, LB_ADDSTRING, 0, (LPARAM)globalSource->GetName()); } } return TRUE; } case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_ADD: { HMENU hMenu = CreatePopupMenu(); for(UINT i=0; iimageSourceClasses.Num(); i++) { if(App->imageSourceClasses[i].strClass != TEXT("GlobalSource")) { if (!App->imageSourceClasses[i].bDeprecated) { String strAdd = Str("Listbox.Add"); strAdd.FindReplace(TEXT("$1"), App->imageSourceClasses[i].strName); AppendMenu(hMenu, MF_STRING, i+1, strAdd.Array()); } } } POINT p; GetCursorPos(&p); int classID = (int)TrackPopupMenuEx(hMenu, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_RIGHTBUTTON, p.x, p.y, hwndMain, NULL); if(!classID) break; String strName; if(OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_ENTERNAME), hwnd, OBS::EnterGlobalSourceNameDialogProc, (LPARAM)&strName) == IDOK) { ClassInfo &ci = App->imageSourceClasses[classID-1]; XElement *globals = App->scenesConfig.GetElement(TEXT("global sources")); if(!globals) globals = App->scenesConfig.CreateElement(TEXT("global sources")); XElement *newSourceElement = globals->CreateElement(strName); newSourceElement->SetString(TEXT("class"), ci.strClass); if(ci.configProc) { if(!ci.configProc(newSourceElement, true)) { globals->RemoveElement(newSourceElement); break; } } SendMessage(GetDlgItem(hwnd, IDC_SOURCES), LB_ADDSTRING, 0, (LPARAM)strName.Array()); } break; } case IDC_REMOVE: { HWND hwndSources = GetDlgItem(hwnd, IDC_SOURCES); HWND hwndSceneSources = GetDlgItem(hwndMain, ID_SOURCES); UINT id = (UINT)SendMessage(GetDlgItem(hwnd, IDC_SOURCES), LB_GETCURSEL, 0, 0); if(id == LB_ERR) break; XElement *globals = App->scenesConfig.GetElement(TEXT("global sources")); if(!globals) break; if(OBSMessageBox(hwnd, Str("GlobalSources.DeleteConfirm"), Str("DeleteConfirm.Title"), MB_YESNO) == IDNO) break; App->EnterSceneMutex(); XElement *element = globals->GetElementByID(id); if(App->bRunning && App->scene && App->scene->sceneItems.Num()) { for(int i=int(App->scene->sceneItems.Num()-1); i>=0; i--) { SceneItem *item = App->scene->sceneItems[i]; if(item->element && scmpi(item->element->GetString(TEXT("class")), TEXT("GlobalSource")) == 0) { XElement *data = item->element->GetElement(TEXT("data")); if(data) { if(scmpi(data->GetString(TEXT("name")), element->GetName()) == 0) { LVFINDINFO findInfo; findInfo.flags = LVFI_STRING; findInfo.psz = (LPCWSTR) item->GetName(); int listID = ListView_FindItem(hwndSceneSources, -1, &findInfo); if(listID != -1) { App->bChangingSources = true; ListView_DeleteItem(hwndSceneSources, listID); App->bChangingSources = false; } App->scene->RemoveImageSource(item); } } } } } XElement *scenes = App->scenesConfig.GetElement(TEXT("scenes")); if(scenes) { UINT numScenes = scenes->NumElements(); for(UINT i=0; iGetElementByID(i); XElement *sources = scene->GetElement(TEXT("sources")); if(sources) { UINT numSources = sources->NumElements(); if (numSources) { for(int j=int(numSources-1); j>=0; j--) { XElement *source = sources->GetElementByID(j); if(scmpi(source->GetString(TEXT("class")), TEXT("GlobalSource")) == 0) { XElement *data = source->GetElement(TEXT("data")); if(data) { CTSTR lpName = data->GetString(TEXT("name")); if(scmpi(lpName, element->GetName()) == 0) { LVFINDINFO findInfo; findInfo.flags = LVFI_STRING; findInfo.psz = (LPCWSTR) source->GetName(); int listID = ListView_FindItem(hwndSceneSources, -1, &findInfo); if(listID != -1) { App->bChangingSources = true; ListView_DeleteItem(hwndSceneSources, listID); App->bChangingSources = false; } sources->RemoveElement(source); } } } } } } } } SendMessage(hwndSources, LB_DELETESTRING, id, 0); if(App->bRunning) { for(UINT i=0; iglobalSources.Num(); i++) { GlobalSourceInfo &info = App->globalSources[i]; if(info.strName.CompareI(element->GetName()) && info.source) { info.FreeData(); App->globalSources.Remove(i); break; } } } globals->RemoveElement(element); App->LeaveSceneMutex(); break; } case IDC_RENAME: { HWND hwndSources = GetDlgItem(hwnd, IDC_SOURCES); UINT id = (UINT)SendMessage(hwndSources, LB_GETCURSEL, 0, 0); if(id == LB_ERR) break; XElement *globals = App->scenesConfig.GetElement(TEXT("global sources")); if(!globals) break; XElement *element = globals->GetElementByID(id); String strName = element->GetName(); if(OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_ENTERNAME), hwndMain, OBS::EnterGlobalSourceNameDialogProc, (LPARAM)&strName) == IDOK) { SendMessage(hwndSources, LB_DELETESTRING, id, 0); SendMessage(hwndSources, LB_INSERTSTRING, id, (LPARAM)strName.Array()); SendMessage(hwndSources, LB_SETCURSEL, id, 0); XElement *scenes = App->scenesConfig.GetElement(TEXT("scenes")); if(scenes) { UINT numScenes = scenes->NumElements(); for(UINT i=0; iGetElementByID(i); XElement *sources = sceneElement->GetElement(TEXT("sources")); if(sources) { UINT numSources = sources->NumElements(); for(UINT j=0; jGetElementByID(j); if(scmpi(source->GetString(TEXT("class")), TEXT("GlobalSource")) == 0) { XElement *data = source->GetElement(TEXT("data")); if(data) { CTSTR lpName = data->GetString(TEXT("name")); if(scmpi(lpName, element->GetName()) == 0) data->SetString(TEXT("name"), strName); } } } } } } element->SetName(strName); } break; } case IDC_IMPORT: { TCHAR lpFile[MAX_PATH + 1]; zero(lpFile, sizeof(lpFile)); OPENFILENAME ofn; zero(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.lpstrFile = lpFile; ofn.hwndOwner = hwndMain; ofn.nMaxFile = MAX_PATH; ofn.lpstrFilter = TEXT("Scene Files (*.xconfig)\0*.xconfig\0"); ofn.nFilterIndex = 1; ofn.lpstrInitialDir = GlobalConfig->GetString(L"General", L"LastImportExportPath"); TCHAR curDirectory[MAX_PATH + 1]; GetCurrentDirectory(MAX_PATH, curDirectory); BOOL bOpenFile = GetOpenFileName(&ofn); SetCurrentDirectory(curDirectory); if(!bOpenFile) break; if(GetPathExtension(lpFile).IsEmpty()) scat(lpFile, L".xconfig"); String strSelectedSceneCollectionGlobalSourcesConfig; strSelectedSceneCollectionGlobalSourcesConfig = lpFile; if(!App->globalSourcesImportConfig.Open(strSelectedSceneCollectionGlobalSourcesConfig)) CrashError(TEXT("Could not open '%s"), strSelectedSceneCollectionGlobalSourcesConfig.Array()); if(OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_GLOBAL_SOURCES_IMPORT), hwnd, OBS::GlobalSourcesImportProc) == IDOK) { HWND hwndSources = GetDlgItem(hwnd, IDC_SOURCES); SendMessage(hwndSources, LB_RESETCONTENT, 0, 0); XElement *globals = App->scenesConfig.GetElement(TEXT("global sources")); if(globals) { UINT numGlobals = globals->NumElements(); for(UINT i = 0; i < numGlobals; i++) { XElement *globalSource = globals->GetElementByID(i); SendMessage(hwndSources, LB_ADDSTRING, 0, (LPARAM)globalSource->GetName()); } } App->globalSourcesImportConfig.Close(); } break; } case IDC_CONFIG: { UINT id = (UINT)SendMessage(GetDlgItem(hwnd, IDC_SOURCES), LB_GETCURSEL, 0, 0); if(id == LB_ERR) break; XElement *globals = App->scenesConfig.GetElement(TEXT("global sources")); if(!globals) break; XElement *element = globals->GetElementByID(id); CTSTR lpClass = element->GetString(TEXT("class")); ClassInfo *imageSourceClass = App->GetImageSourceClass(lpClass); if(imageSourceClass && imageSourceClass->configProc && imageSourceClass->configProc(element, false)) { App->EnterSceneMutex(); if(App->bRunning) { for(UINT i=0; iscene->sceneItems.Num(); i++) { SceneItem *item = App->scene->sceneItems[i]; if(item->element && scmpi(item->element->GetString(TEXT("class")), TEXT("GlobalSource")) == 0) { XElement *data = item->element->GetElement(TEXT("data")); if(data) { if(scmpi(data->GetString(TEXT("name")), element->GetName()) == 0) { if(App->bRunning) { for(UINT i=0; iglobalSources.Num(); i++) { GlobalSourceInfo &info = App->globalSources[i]; if(info.strName.CompareI(element->GetName()) && info.source) info.source->UpdateSettings(); } } item->Update(); } } } } } App->LeaveSceneMutex(); } break; } /*case IDC_MOVEUP: case IDC_MOVEDOWN: case IDC_MOVETOTOP: case IDC_MOVETOBOTTOM: { HWND hwndSources = GetDlgItem(hwnd, IDC_SOURCES); UINT id = (UINT)SendMessage(hwndSources, LB_GETCURSEL, 0, 0); if(id == LB_ERR) break; XElement *globals = App->scenesConfig.GetElement(TEXT("global sources")); if(!globals) break; XElement *element = globals->GetElementByID(id); UINT numElements = globals->NumElements(); if(LOWORD(wParam) == IDC_MOVEUP) { if(id != 0) { SendMessage(hwndSources, LB_DELETESTRING, id--, 0); SendMessage(hwndSources, LB_INSERTSTRING, id, (LPARAM)element->GetName()); PostMessage(hwndSources, LB_SETCURSEL, id, 0); element->MoveUp(); } } else if(LOWORD(wParam) == IDC_MOVEDOWN) { if(id != (numElements-1)) { SendMessage(hwndSources, LB_DELETESTRING, id++, 0); SendMessage(hwndSources, LB_INSERTSTRING, id, (LPARAM)element->GetName()); PostMessage(hwndSources, LB_SETCURSEL, id, 0); element->MoveDown(); } } else if(LOWORD(wParam) == IDC_MOVETOTOP) { if(id != 0) { SendMessage(hwndSources, LB_DELETESTRING, id, 0); SendMessage(hwndSources, LB_INSERTSTRING, 0, (LPARAM)element->GetName()); PostMessage(hwndSources, LB_SETCURSEL, 0, 0); element->MoveToTop(); } } else { if(id != (numElements-1)) { SendMessage(hwndSources, LB_DELETESTRING, id, 0); SendMessage(hwndSources, LB_INSERTSTRING, (numElements-1), (LPARAM)element->GetName()); PostMessage(hwndSources, LB_SETCURSEL, (numElements-1), 0); element->MoveToBottom(); } } break; }*/ case IDOK: { App->scenesConfig.Save(); EndDialog(hwnd, IDOK); } } break; case WM_CLOSE: { App->scenesConfig.Save(); EndDialog(hwnd, IDOK); } } return FALSE; } INT_PTR CALLBACK OBS::GlobalSourcesImportProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: { LocalizeWindow(hwnd); HWND hwndSources = GetDlgItem(hwnd, IDC_SOURCES); XElement *globals = App->globalSourcesImportConfig.GetElement(TEXT("global sources")); if(globals) { UINT numGlobals = globals->NumElements(); for(UINT i=0; iGetElementByID(i); SendMessage(hwndSources, LB_ADDSTRING, 0, (LPARAM)globalSource->GetName()); } } return TRUE; } case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: { HWND hSources = GetDlgItem(hwnd, IDC_SOURCES); int selectedItemsArray[8192]; UINT selectedItems = (UINT)SendMessage(hSources, LB_GETSELITEMS, 512, (LPARAM) selectedItemsArray); if( selectedItems == LB_ERR) break; for(UINT i = 0; i < selectedItems; i++) { XElement *selectedGlobals = App->globalSourcesImportConfig.GetElement(TEXT("global sources")); XElement *selectedGlobalSources = selectedGlobals->GetElementByID(selectedItemsArray[i]); XElement *currentSceneGlobalSources = App->scenesConfig.GetElement(TEXT("global sources")); if(!currentSceneGlobalSources) currentSceneGlobalSources = App->scenesConfig.CreateElement(TEXT("global sources")); if(currentSceneGlobalSources) { XElement *foundGlobalSource = currentSceneGlobalSources->GetElement(selectedGlobalSources->GetName()); if(foundGlobalSource != NULL && selectedGlobalSources->GetName() != foundGlobalSource->GetName()) { String strExists = Str("ImportGlobalSourceNameExists"); strExists.FindReplace(TEXT("$1"), selectedGlobalSources->GetName()); OBSMessageBox(hwnd, strExists, NULL, 0); break; } } XElement *newSourceElement = currentSceneGlobalSources->CopyElement(selectedGlobalSources, selectedGlobalSources->GetName()); newSourceElement->SetString(TEXT("class"), selectedGlobalSources->GetString(TEXT("class"))); } } case IDCANCEL: EndDialog(hwnd, LOWORD(wParam)); break; } } return 0; } //---------------------------- struct ReconnectInfo { UINT_PTR timerID; UINT secondsLeft; }; INT_PTR CALLBACK OBS::ReconnectDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: { LocalizeWindow(hwnd); ReconnectInfo *ri = new ReconnectInfo; ri->secondsLeft = App->reconnectTimeout; ri->timerID = 1; if(!SetTimer(hwnd, 1, 1000, NULL)) { App->bReconnecting = false; EndDialog(hwnd, IDCANCEL); delete ri; return TRUE; } String strText; if(App->bReconnecting) strText << Str("Reconnecting.Retrying") << UIntString(ri->secondsLeft); else strText << Str("Reconnecting") << UIntString(ri->secondsLeft); SetWindowText(GetDlgItem(hwnd, IDC_RECONNECTING), strText); SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)ri); return TRUE; } case WM_TIMER: { ReconnectInfo *ri = (ReconnectInfo*)GetWindowLongPtr(hwnd, DWLP_USER); if(wParam != 1) break; if(!ri->secondsLeft) { if (AppConfig->GetInt(TEXT("Publish"), TEXT("ExperimentalReconnectMode")) == 1 && AppConfig->GetInt(TEXT("Publish"), TEXT("Delay")) == 0) App->RestartNetwork(); else SendMessage(hwndMain, OBS_RECONNECT, 0, 0); EndDialog(hwnd, IDOK); } else { String strText; ri->secondsLeft--; if(App->bReconnecting) strText << Str("Reconnecting.Retrying") << UIntString(ri->secondsLeft); else strText << Str("Reconnecting") << UIntString(ri->secondsLeft); SetWindowText(GetDlgItem(hwnd, IDC_RECONNECTING), strText); } break; } case WM_COMMAND: if(LOWORD(wParam) == IDCANCEL) { App->bReconnecting = false; if (AppConfig->GetInt(TEXT("Publish"), TEXT("ExperimentalReconnectMode")) == 1 && AppConfig->GetInt(TEXT("Publish"), TEXT("Delay")) == 0) App->Stop(); EndDialog(hwnd, IDCANCEL); } break; case WM_CLOSE: App->bReconnecting = false; if (AppConfig->GetInt(TEXT("Publish"), TEXT("ExperimentalReconnectMode")) == 1 && AppConfig->GetInt(TEXT("Publish"), TEXT("Delay")) == 0) App->Stop(); EndDialog(hwnd, IDCANCEL); break; case WM_DESTROY: { ReconnectInfo *ri = (ReconnectInfo*)GetWindowLongPtr(hwnd, DWLP_USER); KillTimer(hwnd, ri->timerID); delete ri; } } return FALSE; } //---------------------------- void OBS::AddProfilesToMenu(HMENU menu) { StringList profileList; GetProfiles(profileList); for(UINT i=0; ibRunning) return; String strCurSceneCollection = GlobalConfig->GetString(TEXT("General"), TEXT("SceneCollection")); String strSceneCollection; if (action == SceneCollectionAction::Rename) strSceneCollection = strCurSceneCollection; if (OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_ENTERNAME), hwndMain, OBS::EnterSceneCollectionDialogProc, (LPARAM)&strSceneCollection) != IDOK) return; App->scenesConfig.SaveTo(String() << lpAppDataPath << "\\scenes.xconfig"); App->scenesConfig.Save(); String strCurSceneCollectionPath; strCurSceneCollectionPath = FormattedString(L"%s\\sceneCollection\\%s.xconfig", lpAppDataPath, strCurSceneCollection.Array()); String strSceneCollectionPath; strSceneCollectionPath << lpAppDataPath << TEXT("\\sceneCollection\\") << strSceneCollection << TEXT(".xconfig"); if ((action != SceneCollectionAction::Rename || !strSceneCollectionPath.CompareI(strCurSceneCollectionPath)) && OSFileExists(strSceneCollectionPath)) OBSMessageBox(hwndMain, Str("Settings.General.ScenesExists"), NULL, 0); else { bool success = true; App->scenesConfig.Close(true); if (action == SceneCollectionAction::Rename) { if (!MoveFile(strCurSceneCollectionPath, strSceneCollectionPath)) success = false; } else if (action == SceneCollectionAction::Clone) { if (!CopyFileW(strCurSceneCollectionPath, strSceneCollectionPath, TRUE)) success = false; } else { if (!App->scenesConfig.Open(strSceneCollectionPath)) { OBSMessageBox(hwndMain, TEXT("Error - unable to create new Scene Collection, could not create file"), NULL, 0); success = false; } } if (!success) { App->scenesConfig.Open(strCurSceneCollectionPath); return; } GlobalConfig->SetString(TEXT("General"), TEXT("SceneCollection"), strSceneCollection); App->ReloadSceneCollection(); App->ResetSceneCollectionMenu(); App->ResetApplicationName(); } } void OBS::RemoveSceneCollection() { if (App->bRunning) return; String strCurSceneCollection = GlobalConfig->GetString(TEXT("General"), TEXT("SceneCollection")); String strCurSceneCollectionFile = strCurSceneCollection + L".xconfig"; String strCurSceneCollectionDir; strCurSceneCollectionDir << lpAppDataPath << TEXT("\\sceneCollection\\"); OSFindData ofd; HANDLE hFind = OSFindFirstFile(strCurSceneCollectionDir + L"*.xconfig", ofd); if (!hFind) { Log(L"Find failed for scene collections"); return; } String nextFile; do { if (scmpi(ofd.fileName, strCurSceneCollectionFile) != 0) { nextFile = ofd.fileName; break; } } while (OSFindNextFile(hFind, ofd)); OSFindClose(hFind); if (nextFile.IsEmpty()) return; String strConfirm = Str("Settings.General.ConfirmDelete"); strConfirm.FindReplace(TEXT("$1"), strCurSceneCollection); if (OBSMessageBox(hwndMain, strConfirm, Str("DeleteConfirm.Title"), MB_YESNO) == IDYES) { String strCurSceneCollectionPath; strCurSceneCollectionPath << strCurSceneCollectionDir << strCurSceneCollection << TEXT(".xconfig"); OSDeleteFile(strCurSceneCollectionPath); App->scenesConfig.Close(); GlobalConfig->SetString(L"General", L"SceneCollection", GetPathWithoutExtension(nextFile)); App->ReloadSceneCollection(); App->ResetSceneCollectionMenu(); } } void OBS::ImportSceneCollection() { if (OBSMessageBox(hwndMain, Str("ImportCollectionReplaceWarning.Text"), Str("ImportCollectionReplaceWarning.Title"), MB_YESNO) == IDNO) return; TCHAR lpFile[MAX_PATH+1]; zero(lpFile, sizeof(lpFile)); OPENFILENAME ofn; zero(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.lpstrFile = lpFile; ofn.hwndOwner = hwndMain; ofn.nMaxFile = MAX_PATH; ofn.lpstrFilter = TEXT("Scene Files (*.xconfig)\0*.xconfig\0"); ofn.nFilterIndex = 1; ofn.lpstrInitialDir = GlobalConfig->GetString(L"General", L"LastImportExportPath"); TCHAR curDirectory[MAX_PATH+1]; GetCurrentDirectory(MAX_PATH, curDirectory); BOOL bOpenFile = GetOpenFileName(&ofn); SetCurrentDirectory(curDirectory); if (!bOpenFile) return; if (GetPathExtension(lpFile).IsEmpty()) scat(lpFile, L".xconfig"); GlobalConfig->SetString(L"General", L"LastImportExportPath", GetPathDirectory(lpFile)); String strCurSceneCollection = GlobalConfig->GetString(TEXT("General"), TEXT("SceneCollection")); String strCurSceneCollectionFile; strCurSceneCollectionFile << lpAppDataPath << TEXT("\\sceneCollection\\") << strCurSceneCollection << L".xconfig"; scenesConfig.Close(); CopyFile(lpFile, strCurSceneCollectionFile, false); App->ReloadSceneCollection(); } void OBS::ExportSceneCollection() { TCHAR lpFile[MAX_PATH+1]; zero(lpFile, sizeof(lpFile)); OPENFILENAME ofn; zero(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(ofn); ofn.lpstrFile = lpFile; ofn.hwndOwner = hwndMain; ofn.nMaxFile = MAX_PATH; ofn.lpstrFilter = TEXT("Scene Files (*.xconfig)\0*.xconfig\0"); ofn.nFilterIndex = 1; ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; ofn.lpstrInitialDir = GlobalConfig->GetString(L"General", L"LastImportExportPath"); TCHAR curDirectory[MAX_PATH+1]; GetCurrentDirectory(MAX_PATH, curDirectory); BOOL bSaveFile = GetSaveFileName(&ofn); SetCurrentDirectory(curDirectory); if (!bSaveFile) return; if (GetPathExtension(lpFile).IsEmpty()) scat(lpFile, L".xconfig"); GlobalConfig->SetString(L"General", L"LastImportExportPath", GetPathDirectory(lpFile)); scenesConfig.SaveTo(lpFile); } //---------------------------- void OBS::ResetSceneCollectionMenu() { HMENU hmenuMain = GetMenu(hwndMain); HMENU hmenuSceneCollection = GetSubMenu(hmenuMain, 3); while (DeleteMenu(hmenuSceneCollection, 8, MF_BYPOSITION)); AddSceneCollectionToMenu(hmenuSceneCollection); } void OBS::ResetProfileMenu() { HMENU hmenuMain = GetMenu(hwndMain); HMENU hmenuProfiles = GetSubMenu(hmenuMain, 2); while (DeleteMenu(hmenuProfiles, 0, MF_BYPOSITION)); AddProfilesToMenu(hmenuProfiles); } //---------------------------- void OBS::DisableMenusWhileStreaming(bool disable) { HMENU hmenuMain = GetMenu(hwndMain); EnableMenuItem(hmenuMain, 2, (!disable ? MF_ENABLED : MF_DISABLED) | MF_BYPOSITION); EnableMenuItem(hmenuMain, 3, (!disable ? MF_ENABLED : MF_DISABLED) | MF_BYPOSITION); EnableMenuItem(hmenuMain, ID_HELP_UPLOAD_CURRENT_LOG, (!disable ? MF_ENABLED : MF_DISABLED)); EnableMenuItem(hmenuMain, ID_HELP_ANALYZE_CURRENT_LOG, (!disable ? MF_ENABLED : MF_DISABLED)); DrawMenuBar(hwndMain); } //---------------------------- void LogUploadMonitorCallback() { PostMessage(hwndMain, WM_COMMAND, MAKEWPARAM(ID_REFRESH_LOGS, 0), 0); } //---------------------------- static HMENU FindParent(HMENU root, UINT id, String *name=nullptr) { MENUITEMINFO info; zero(&info, sizeof(info)); info.cbSize = sizeof(info); info.fMask = MIIM_SUBMENU | (name ? MIIM_STRING : 0); MENUITEMINFO verifier; zero(&verifier, sizeof(verifier)); verifier.cbSize = sizeof(verifier); bool found = false; int count = GetMenuItemCount(root); for (int i = 0; i < count; i++) { info.cch = 0; if (!GetMenuItemInfo(root, i, true, &info)) continue; if (!info.hSubMenu) continue; HMENU submenu = info.hSubMenu; if (!GetMenuItemInfo(submenu, id, false, &verifier)) continue; if (name) { name->SetLength(info.cch++); info.dwTypeData = name->Array(); GetMenuItemInfo(root, i, true, &info); info.dwTypeData = nullptr; } found = true; root = submenu; i = 0; count = GetMenuItemCount(root); } return found ? root : nullptr; } void OBS::ResetLogUploadMenu() { String logfilePattern = FormattedString(L"%s/logs/*.log", OBSGetAppDataPath()); std::vectorlogFiles.cbegin())> validLogs; OSFindData ofd; HANDLE finder; if (!App->logDirectoryMonitor) { App->logDirectoryMonitor = OSMonitorDirectoryCallback(String(OBSGetAppDataPath()) << L"/logs/", LogUploadMonitorCallback); if (!(finder = OSFindFirstFile(logfilePattern, ofd))) return; char *contents = (char*)Allocate(1024 * 8); do { if (ofd.bDirectory) continue; String filename = GetPathFileName(ofd.fileName, true); auto iter = App->logFiles.emplace(filename.Array(), false).first; XFile f(String(OBSGetAppDataPath()) << L"/logs/" << filename, XFILE_READ | XFILE_SHARED, XFILE_OPENEXISTING); if (!f.IsOpen()) continue; DWORD nRead = f.Read(contents, 1024*8 - 1); contents[nRead] = 0; bool validLog = (strstr(contents, "Open Broadcaster Software") != nullptr); if (!validLog) continue; iter->second = true; validLogs.push_back(iter); } while (OSFindNextFile(finder, ofd)); Free(contents); } else { if (finder = OSFindFirstFile(logfilePattern, ofd)) { auto previous = std::move(App->logFiles); App->logFiles.clear(); do { if (ofd.bDirectory) continue; std::wstring log = GetPathFileName(ofd.fileName, true); if (previous.find(log) == previous.end()) continue; if (!(App->logFiles[log] = previous[log])) continue; validLogs.push_back(App->logFiles.find(log)); } while (OSFindNextFile(finder, ofd)); } else App->logFiles.clear(); } HMENU hmenuMain = GetMenu(hwndMain); HMENU hmenuUpload = FindParent(hmenuMain, ID_HELP_UPLOAD_CURRENT_LOG); if (!hmenuUpload) return; while (DeleteMenu(hmenuUpload, 2, MF_BYPOSITION)); if (validLogs.empty()) return; AppendMenu(hmenuUpload, MF_SEPARATOR, 0, nullptr); AppendMenu(hmenuUpload, MF_STRING, ID_UPLOAD_ANALYZE_LOG, Str("MainMenu.Help.AnalyzeLastLog")); AppendMenu(hmenuUpload, MF_STRING, ID_UPLOAD_LOG, Str("MainMenu.Help.UploadLastLog")); AppendMenu(hmenuUpload, MF_SEPARATOR, 0, nullptr); unsigned i = 0; for (auto iter = validLogs.rbegin(); iter != validLogs.rend(); i++, iter++) { HMENU items = CreateMenu(); AppendMenu(items, MF_STRING, ID_UPLOAD_ANALYZE_LOG + i, Str("LogUpload.Analyze")); AppendMenu(items, MF_STRING, ID_UPLOAD_LOG + i, Str("LogUpload.Upload")); AppendMenu(items, MF_STRING, ID_VIEW_LOG + i, Str("LogUpload.View")); AppendMenu(hmenuUpload, MF_STRING | MF_POPUP, (UINT_PTR)items, (*iter)->first.c_str()); } } //---------------------------- String GetLogUploadMenuItem(UINT item) { HMENU hmenuMain = GetMenu(hwndMain); String log; FindParent(hmenuMain, item, &log); return log; } //---------------------------- namespace { struct HLOCALDeleter { void operator()(void *h) { GlobalFree(h); } }; struct MemUnlocker { void operator()(void *m) { GlobalUnlock(m); } }; struct ClipboardHelper { ClipboardHelper(HWND owner) { OpenClipboard(owner); } ~ClipboardHelper() { CloseClipboard(); } bool Insert(String &str) { using namespace std; if (!EmptyClipboard()) return false; unique_ptr h(GlobalAlloc(LMEM_MOVEABLE, (str.Length() + 1) * sizeof TCHAR)); if (!h) return false; unique_ptr mem(GlobalLock(h.get())); if (!mem) return false; tstr_to_wide(str.Array(), (wchar_t*)mem.get(), str.Length() + 1); if (!SetClipboardData(CF_UNICODETEXT, mem.get())) return false; h.release(); return true; } bool Contains(String &str) { using namespace std; HANDLE h = GetClipboardData(CF_UNICODETEXT); if (!h) return false; unique_ptr mem(GlobalLock(h)); if (!mem) return false; return !!str.Compare((CTSTR)mem.get()); } }; } INT_PTR CALLBACK LogUploadResultProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_INITDIALOG: { LocalizeWindow(hwnd); HWND hwndOwner = GetParent(hwnd); if (!hwndOwner) hwndOwner = GetDesktopWindow(); RECT rc, rcDlg, rcOwner; GetWindowRect(hwndOwner, &rcOwner); GetWindowRect(hwnd, &rcDlg); CopyRect(&rc, &rcOwner); // Offset the owner and dialog box rectangles so that right and bottom // values represent the width and height, and then offset the owner again // to discard space taken up by the dialog box. OffsetRect(&rcDlg, -rcDlg.left, -rcDlg.top); OffsetRect(&rc, -rc.left, -rc.top); OffsetRect(&rc, -rcDlg.right, -rcDlg.bottom); // The new position is the sum of half the remaining space and the owner's // original position. SetWindowPos(hwnd, HWND_TOP, rcOwner.left + (rc.right / 2), rcOwner.top + (rc.bottom / 2), 0, 0, // Ignores size arguments. SWP_NOSIZE); LogUploadResult &result = *(LogUploadResult*)lParam; SetWindowText(GetDlgItem(hwnd, IDC_URL), result.url.Array()); AddClipboardFormatListener(hwnd); SetWindowLongPtr(hwnd, DWLP_USER, (LONG_PTR)lParam); if (result.openAnalyzerOnSuccess) PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_ANALYZE, 0), 0); return TRUE; } case WM_CLIPBOARDUPDATE: { LONG_PTR ptr = GetWindowLongPtr(hwnd, DWLP_USER); if (!ptr) break; ClipboardHelper clip(hwnd); if (!clip.Contains(((LogUploadResult*)ptr)->url)) ShowWindow(GetDlgItem(hwnd, IDC_COPIED), SW_HIDE); else { SetWindowText(GetDlgItem(hwnd, IDC_COPIED), Str("LogUpload.SuccessDialog.CopySuccess")); ShowWindow(GetDlgItem(hwnd, IDC_COPIED), SW_SHOW); } } case WM_COMMAND: switch (LOWORD(wParam)) { case IDC_COPY: { LONG_PTR ptr = GetWindowLongPtr(hwnd, DWLP_USER); if (!ptr) break; ClipboardHelper clip(hwnd); if (clip.Insert(((LogUploadResult*)ptr)->url)) SetWindowText(GetDlgItem(hwnd, IDC_COPIED), Str("LogUpload.SuccessDialog.CopySuccess")); else SetWindowText(GetDlgItem(hwnd, IDC_COPIED), Str("LogUpload.SuccessDialog.CopyFailure")); ShowWindow(GetDlgItem(hwnd, IDC_COPIED), SW_SHOW); } break; case IDOK: SendMessage(hwnd, WM_CLOSE, 0, 0); break; case IDC_ANALYZE: { LONG_PTR ptr = GetWindowLongPtr(hwnd, DWLP_USER); if (!ptr) break; String url = CreateHTTPURL(L"obsproject.com", L"/analyzer", FormattedString(L"?url=%s", ((LogUploadResult*)ptr)->analyzerURL.Array())); if (url.IsEmpty()) break; if (!ShellExecute(nullptr, nullptr, url.Array(), nullptr, nullptr, SW_SHOWDEFAULT)) OBSMessageBox(hwnd, Str("LogUploader.FailedToAnalyze"), nullptr, MB_ICONERROR); break; } } break; case WM_CTLCOLORSTATIC: if (GetDlgCtrlID((HWND)lParam) == IDC_COPIED) { LONG_PTR ptr = GetWindowLongPtr(hwnd, DWLP_USER); if (!ptr) break; ClipboardHelper clip(hwnd); if (clip.Contains(((LogUploadResult*)ptr)->url)) SetTextColor((HDC)wParam, RGB(0, 200, 0)); else SetTextColor((HDC)wParam, RGB(200, 0, 0)); SetBkColor((HDC)wParam, COLORREF(GetSysColor(COLOR_3DFACE))); return (INT_PTR)GetSysColorBrush(COLOR_3DFACE); } break; case WM_CLOSE: EndDialog(hwnd, 0); break; case WM_DESTROY: RemoveClipboardFormatListener(hwnd); } return FALSE; } void ShowLogUploadResult(LogUploadResult &result, bool success) { if (!success) { OBSMessageBox(hwndMain, result.errors.Array(), nullptr, MB_ICONEXCLAMATION); return; } OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_LOGUPLOADED), hwndMain, LogUploadResultProc, (LPARAM)&result); } //---------------------------- static void OBSUpdateLog() { static unsigned position = 0; String content; ReadLogPartial(content, position); if (content.IsEmpty()) return; int start, end; SendMessage(hwndLog, EM_GETSEL, (WPARAM)&start, (LPARAM)&end); SendMessage(hwndLog, EM_SETSEL, INT_MAX, INT_MAX); SendMessage(hwndLog, EM_REPLACESEL, 0, (LPARAM)content.Array()); SendMessage(hwndLog, EM_SETSEL, start, end); App->ReportLogUpdate(content.Array(), content.Length()); } //---------------------------- String OBS::GetApplicationName() { String name; // we hide the bit version on 32 bit to avoid confusing users who have a 64 // bit pc unncessarily asking for the 64 bit version under the assumption // that the 32 bit version doesn't work or something. name << "Profile: " << App->GetCurrentProfile() << " - " << "Scenes: " << App->GetCurrentSceneCollection() << L" - " OBS_VERSION_STRING #ifdef _WIN64 L" - 64bit"; #else L""; #endif return name; } //---------------------------- void OBS::ResetApplicationName() { SetWindowText(hwndMain, GetApplicationName()); } //---------------------------- LRESULT CALLBACK OBS::OBSProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_COMMAND: switch(LOWORD(wParam)) { case ID_SETTINGS_SETTINGS: case ID_SETTINGS: OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_SETTINGS), hwnd, (DLGPROC)OBS::SettingsDialogProc); break; case ID_GLOBALSOURCES: OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_GLOBAL_SOURCES), hwnd, (DLGPROC)OBS::GlobalSourcesProc); break; case ID_FILE_SAVE2: App->scenesConfig.SaveTo(String() << lpAppDataPath << "\\scenes.xconfig"); App->scenesConfig.Save(); break; case ID_TOGGLERECORDING: App->RefreshStreamButtons(true); App->ToggleRecording(); App->RefreshStreamButtons(); break; case ID_TOGGLERECORDINGREPLAYBUFFER: App->RefreshStreamButtons(true); App->ToggleReplayBuffer(); App->RefreshStreamButtons(); break; case ID_FILE_EXIT: case ID_EXIT: PostQuitMessage(0); break; case ID_RECORDINGSFOLDER: case ID_SAVEDREPLAYBUFFERS: { String path = OSGetDefaultVideoSavePath(); path = AppConfig->GetString(L"Publish", LOWORD(wParam) == ID_SAVEDREPLAYBUFFERS ? L"ReplayBufferSavePath" : L"SavePath", path.Array()); path = GetExpandedRecordingDirectoryBase(path).FindReplace(L"/", L"\\"); String lastFile = App->lastOutputFile.FindReplace(L"/", L"\\"); LPITEMIDLIST item = nullptr; if (lastFile.IsValid() && path == lastFile.Left(path.Length()) && OSFileExists(lastFile) && (item = ILCreateFromPath(lastFile))) { SHOpenFolderAndSelectItems(item, 0, nullptr, 0); ILFree(item); } else ShellExecute(NULL, L"open", path, 0, 0, SW_SHOWNORMAL); break; } case ID_ALWAYSONTOP: { HMENU hmenuBar = GetMenu(hwnd); App->bAlwaysOnTop = !App->bAlwaysOnTop; CheckMenuItem(hmenuBar, ID_ALWAYSONTOP, (App->bAlwaysOnTop)?MF_CHECKED:MF_UNCHECKED); SetWindowPos(hwndMain, (App->bAlwaysOnTop)?HWND_TOPMOST:HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE); } break; case ID_FULLSCREENMODE: App->SetFullscreenMode(!App->bFullscreenMode); break; case ID_SHOWLOG: ShowWindow(hwndLogWindow, SW_SHOW); SetForegroundWindow(hwndLogWindow); break; case ID_HELP_VISITWEBSITE: ShellExecute(NULL, TEXT("open"), TEXT("http://www.obsproject.com"), 0, 0, SW_SHOWNORMAL); break; case ID_HELP_OPENHELP: ShellExecute(NULL, TEXT("open"), TEXT("http://jp9000.github.io/OBS/"), 0, 0, SW_SHOWNORMAL); break; case ID_HELP_CHECK_FOR_UPDATES: OSCloseThread(OSCreateThread((XTHREAD)CheckUpdateThread, (LPVOID)1)); break; case ID_HELP_ANALYZE_CURRENT_LOG: case ID_HELP_UPLOAD_CURRENT_LOG: if (App->bRunning) break; { LogUploadResult result; result.openAnalyzerOnSuccess = LOWORD(wParam) == ID_HELP_ANALYZE_CURRENT_LOG; ShowLogUploadResult(result, UploadCurrentLog(result)); break; } case ID_REFRESH_LOGS: App->ResetLogUploadMenu(); break; /*case ID_DASHBOARD: ShellExecute(NULL, TEXT("open"), App->strDashboard, 0, 0, SW_SHOWNORMAL); break;*/ case ID_SETTINGS_OPENCONFIGFOLDER: { String strAppPath = API->GetAppDataPath(); ShellExecute(NULL, TEXT("open"), strAppPath, 0, 0, SW_SHOWNORMAL); } break; case ID_SETTINGS_OPENLOGFOLDER: { String strAppPath = API->GetAppDataPath(); strAppPath << TEXT("\\logs"); ShellExecute(NULL, TEXT("open"), strAppPath, 0, 0, SW_SHOWNORMAL); } break; case ID_PLUGINS: OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_PLUGINS), hwnd, (DLGPROC)OBS::PluginsDialogProc); break; case ID_MICVOLUME: if(HIWORD(wParam) == VOLN_ADJUSTING || HIWORD(wParam) == VOLN_FINALVALUE) { if(IsWindowEnabled((HWND)lParam)) { App->micVol = GetVolumeControlValue((HWND)lParam); bool finalValue = HIWORD(wParam) == VOLN_FINALVALUE; App->ReportMicVolumeChange(App->micVol, App->micVol < VOLN_MUTELEVEL, finalValue); if(App->micVol < EPSILON) App->micVol = 0.0f; if(finalValue) { AppConfig->SetFloat(TEXT("Audio"), TEXT("MicVolume"), App->micVol); if (App->micVol < EPSILON) AppConfig->SetFloat(TEXT("Audio"), TEXT("MicMutedVolume"), GetVolumeControlMutedVal((HWND)lParam)); } } } break; case ID_DESKTOPVOLUME: if(HIWORD(wParam) == VOLN_ADJUSTING || HIWORD(wParam) == VOLN_FINALVALUE) { if(IsWindowEnabled((HWND)lParam)) { App->desktopVol = GetVolumeControlValue((HWND)lParam); bool finalValue = HIWORD(wParam) == VOLN_FINALVALUE; App->ReportDesktopVolumeChange(App->desktopVol, App->desktopVol < VOLN_MUTELEVEL, finalValue); if(App->desktopVol < EPSILON) App->desktopVol = 0.0f; if(finalValue) { AppConfig->SetFloat(TEXT("Audio"), TEXT("DesktopVolume"), App->desktopVol); if (App->desktopVol < EPSILON) AppConfig->SetFloat(TEXT("Audio"), TEXT("DesktopMutedVolume"), GetVolumeControlMutedVal((HWND)lParam)); } } } break; case ID_DESKTOPVOLUMEMETER: case ID_MICVOLUMEMETER: if(HIWORD(wParam) == VOLN_METERED) { App->UpdateAudioMeters(); } break; case ID_SCENEEDITOR: if(HIWORD(wParam) == BN_CLICKED) App->bEditMode = !App->bEditMode; break; case ID_SCENES: if(HIWORD(wParam) == LBN_SELCHANGE) { HWND hwndScenes = (HWND)lParam; UINT id = (UINT)SendMessage(hwndScenes, LB_GETCURSEL, 0, 0); if(id != LB_ERR) { String strName; strName.SetLength((UINT)SendMessage(hwndScenes, LB_GETTEXTLEN, id, 0)); SendMessage(hwndScenes, LB_GETTEXT, id, (LPARAM)strName.Array()); App->SetScene(strName); } } break; case ID_SCENECOLLECTION_NEW: App->AddSceneCollection(SceneCollectionAction::Add); break; case ID_SCENECOLLECTION_CLONE: App->AddSceneCollection(SceneCollectionAction::Clone); break; case ID_SCENECOLLECTION_RENAME: App->AddSceneCollection(SceneCollectionAction::Rename); break; case ID_SCENECOLLECTION_REMOVE: App->RemoveSceneCollection(); break; case ID_SCENECOLLECTION_IMPORT: App->ImportSceneCollection(); break; case ID_SCENECOLLECTION_EXPORT: App->ExportSceneCollection(); break; case ID_TESTSTREAM: App->RefreshStreamButtons(true); App->bTestStream = true; App->ToggleCapturing(); App->RefreshStreamButtons(); break; case ID_STARTSTOP: App->RefreshStreamButtons(true); App->ToggleCapturing(); App->RefreshStreamButtons(); break; case ID_MINIMIZERESTORE: { bool bMinimizeToNotificationArea = AppConfig->GetInt(TEXT("General"), TEXT("MinimizeToNotificationArea"), 0) != 0; if (bMinimizeToNotificationArea) { if (IsWindowVisible(hwnd)) { ShowWindow(hwnd, SW_MINIMIZE); ShowWindow(hwnd, SW_HIDE); } else { ShowWindow(hwnd, SW_SHOW); ShowWindow(hwnd, SW_RESTORE); } } else { if (IsIconic(hwnd)) ShowWindow(hwnd, SW_RESTORE); else ShowWindow(hwnd, SW_MINIMIZE); } } break; case IDA_SOURCE_DELETE: App->DeleteItems(); break; case IDA_SOURCE_CENTER: App->CenterItems(true, true); break; case IDA_SOURCE_CENTER_VER: App->CenterItems(false, true); break; case IDA_SOURCE_CENTER_HOR: App->CenterItems(true, false); break; case IDA_SOURCE_LEFT_CANVAS: App->MoveItemsToEdge(-1, 0); break; case IDA_SOURCE_TOP_CANVAS: App->MoveItemsToEdge(0, -1); break; case IDA_SOURCE_RIGHT_CANVAS: App->MoveItemsToEdge(1, 0); break; case IDA_SOURCE_BOTTOM_CANVAS: App->MoveItemsToEdge(0, 1); break; case IDA_SOURCE_FITTOSCREEN: App->FitItemsToScreen(); break; case IDA_SOURCE_RESETSIZE: App->ResetItemSizes(); break; case IDA_SOURCE_RESETCROP: App->ResetItemCrops(); break; case IDA_SOURCE_MOVEUP: App->MoveSourcesUp(); break; case IDA_SOURCE_MOVEDOWN: App->MoveSourcesDown(); break; case IDA_SOURCE_MOVETOTOP: App->MoveSourcesToTop(); break; case IDA_SOURCE_MOVETOBOTTOM: App->MoveSourcesToBottom(); break; case IDCANCEL: // This actually means the user pressed the ESC key if(App->bFullscreenMode) App->SetFullscreenMode(false); break; default: { UINT id = LOWORD(wParam); if (id >= ID_SWITCHPROFILE && id <= ID_SWITCHPROFILE_END) { if (App->bRunning) break; MENUITEMINFO mii; zero(&mii, sizeof(mii)); mii.cbSize = sizeof(mii); mii.fMask = MIIM_STRING; HMENU hmenuMain = GetMenu(hwndMain); HMENU hmenuProfiles = GetSubMenu(hmenuMain, 2); GetMenuItemInfo(hmenuProfiles, id, FALSE, &mii); String strProfile; strProfile.SetLength(mii.cch++); mii.dwTypeData = strProfile.Array(); GetMenuItemInfo(hmenuProfiles, id, FALSE, &mii); if(!strProfile.CompareI(GetCurrentProfile())) { String strProfilePath; strProfilePath << lpAppDataPath << TEXT("\\profiles\\") << strProfile << TEXT(".ini"); if(!AppConfig->Open(strProfilePath)) { OBSMessageBox(hwnd, TEXT("Error - unable to open ini file"), NULL, 0); break; } GlobalConfig->SetString(TEXT("General"), TEXT("Profile"), strProfile); App->ReloadIniSettings(); ResetProfileMenu(); ResetApplicationName(); } } else if (id >= ID_SWITCHSCENECOLLECTION && id <= ID_SWITCHSCENECOLLECTION_END) { if (App->bRunning) break; MENUITEMINFO mii; zero(&mii, sizeof(mii)); mii.cbSize = sizeof(mii); mii.fMask = MIIM_STRING; HMENU hmenuMain = GetMenu(hwndMain); HMENU hmenuSceneCollection = GetSubMenu(hmenuMain, 3); GetMenuItemInfo(hmenuSceneCollection, id, FALSE, &mii); String strSceneCollection; strSceneCollection.SetLength(mii.cch++); mii.dwTypeData = strSceneCollection.Array(); GetMenuItemInfo(hmenuSceneCollection, id, FALSE, &mii); if (!strSceneCollection.CompareI(GetCurrentSceneCollection())) { App->scenesConfig.Save(); CTSTR collection = GetCurrentSceneCollection(); String strSceneCollectionPath; strSceneCollectionPath = FormattedString(L"%s\\sceneCollection\\%s.xconfig", lpAppDataPath, collection); if (!App->scenesConfig.Open(strSceneCollectionPath)) { OBSMessageBox(hwnd, TEXT("Error - unable to open xconfig file"), NULL, 0); break; } GlobalConfig->SetString(TEXT("General"), TEXT("SceneCollection"), strSceneCollection); App->scenesConfig.Close(); App->ReloadSceneCollection(); ResetSceneCollectionMenu(); ResetApplicationName(); App->UpdateNotificationAreaIcon(); App->scenesConfig.SaveTo(String() << lpAppDataPath << "\\scenes.xconfig"); } } else if (id >= ID_UPLOAD_LOG && id <= ID_UPLOAD_LOG_END) { String log = GetLogUploadMenuItem(id); if (log.IsEmpty()) break; LogUploadResult result; ShowLogUploadResult(result, UploadLog(log, result)); } else if (id >= ID_UPLOAD_ANALYZE_LOG && id <= ID_UPLOAD_ANALYZE_LOG_END) { String log = GetLogUploadMenuItem(id); if (log.IsEmpty()) break; LogUploadResult result; result.openAnalyzerOnSuccess = true; ShowLogUploadResult(result, UploadLog(log, result)); } else if (id >= ID_VIEW_LOG && id <= ID_VIEW_LOG_END) { String log = GetLogUploadMenuItem(id); if (log.IsEmpty()) break; String tar = FormattedString(L"%s\\logs\\%s", OBSGetAppDataPath(), log.Array()); HINSTANCE result = ShellExecute(nullptr, L"edit", tar.Array(), 0, 0, SW_SHOWNORMAL); if (result > (HINSTANCE)32) break; result = ShellExecute(nullptr, nullptr, tar.Array(), 0, 0, SW_SHOWNORMAL); if (result > (HINSTANCE)32) break; if (result == (HINSTANCE)SE_ERR_NOASSOC || result == (HINSTANCE)SE_ERR_ASSOCINCOMPLETE) { OPENASINFO info; info.pcszFile = tar.Array(); info.pcszClass = nullptr; info.oaifInFlags = OAIF_ALLOW_REGISTRATION | OAIF_EXEC; if (SHOpenWithDialog(nullptr, &info) == S_OK) break; } String error = Str("Sources.TextSource.FileNotFound"); OBSMessageBox(hwndMain, error.FindReplace(L"$1", tar.Array()).Array(), nullptr, MB_ICONERROR); } } } break; case WM_NOTIFY: { NMHDR nmh = *(LPNMHDR)lParam; switch(wParam) { case ID_SOURCES: if(nmh.code == NM_CUSTOMDRAW) { LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam; switch( lplvcd->nmcd.dwDrawStage) { case CDDS_ITEMPREPAINT: int state, bkMode; BOOL checkedState; RECT iconRect, textRect, itemRect, gsVIRect; COLORREF oldTextColor; String itemText; HDC hdc = lplvcd->nmcd.hdc; int itemId = (int)lplvcd->nmcd.dwItemSpec; bool isGlobalSource = false; XElement *sources, *sourcesElement = NULL; sources = App->sceneElement->GetElement(TEXT("sources")); if(sources) { sourcesElement = sources->GetElementByID(itemId); if(sourcesElement) if(scmpi(sourcesElement->GetString(TEXT("class")), TEXT("GlobalSource")) == 0) isGlobalSource = true; } ListView_GetItemRect(nmh.hwndFrom,itemId, &itemRect, LVIR_BOUNDS); ListView_GetItemRect(nmh.hwndFrom,itemId, &textRect, LVIR_LABEL); iconRect.right = textRect.left - 1; iconRect.left = iconRect.right - (textRect.bottom - textRect.top); iconRect.top = textRect.top + 2; iconRect.bottom = textRect.bottom - 2; gsVIRect.left = 1; gsVIRect.right = 2; gsVIRect.top = iconRect.top + 1; gsVIRect.bottom = iconRect.bottom - 1; state = ListView_GetItemState(nmh.hwndFrom, itemId, LVIS_SELECTED); checkedState = ListView_GetCheckState(nmh.hwndFrom, itemId); oldTextColor = GetTextColor(hdc); if(state&LVIS_SELECTED) { FillRect(hdc, &itemRect, (HBRUSH)(COLOR_HIGHLIGHT + 1)); SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); } else { FillRect(hdc, &itemRect, (HBRUSH)(COLOR_WINDOW + 1)); SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); } HTHEME hTheme = OpenThemeData(hwnd, TEXT("BUTTON")); if(hTheme) { if(checkedState) DrawThemeBackground(hTheme, hdc, BP_CHECKBOX, (state&LVIS_SELECTED)?CBS_CHECKEDPRESSED:CBS_CHECKEDNORMAL, &iconRect, NULL); else DrawThemeBackground(hTheme, hdc, BP_CHECKBOX, CBS_UNCHECKEDNORMAL, &iconRect, NULL); CloseThemeData(hTheme); } else { iconRect.right = iconRect.left + iconRect.bottom - iconRect.top; if(checkedState) DrawFrameControl(hdc,&iconRect, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_CHECKED | DFCS_FLAT); else DrawFrameControl(hdc,&iconRect, DFC_BUTTON, DFCS_BUTTONCHECK | DFCS_FLAT); } if(isGlobalSource) FillRect(hdc, &gsVIRect, (state&LVIS_SELECTED) ? (HBRUSH)(COLOR_HIGHLIGHTTEXT + 1) : (HBRUSH)(COLOR_WINDOWTEXT + 1)); textRect.left += 2; if(sourcesElement) { itemText = sourcesElement->GetName(); if(itemText.IsValid()) { bkMode = SetBkMode(hdc, TRANSPARENT); DrawText(hdc, itemText, slen(itemText), &textRect, DT_LEFT | DT_END_ELLIPSIS | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX ); SetBkMode(hdc, bkMode); } } SetTextColor(hdc, oldTextColor); return CDRF_SKIPDEFAULT; } return CDRF_NOTIFYPOSTPAINT | CDRF_NOTIFYITEMDRAW; } if(nmh.code == LVN_ITEMCHANGED && !App->bChangingSources) { NMLISTVIEW pnmv = *(LPNMLISTVIEW)lParam; if((pnmv.uOldState & LVIS_SELECTED) != (pnmv.uNewState & LVIS_SELECTED)) { /*item selected*/ App->SelectSources(); } if((pnmv.uOldState & LVIS_STATEIMAGEMASK) != (pnmv.uNewState & LVIS_STATEIMAGEMASK)) { //checks changed App->CheckSources(); } } else if(nmh.code == NM_DBLCLK) { NMITEMACTIVATE itemActivate = *(LPNMITEMACTIVATE)lParam; UINT selectedID = itemActivate.iItem; /* check to see if the double click is on top of the checkbox if so, forget about it */ LVHITTESTINFO hitInfo; hitInfo.pt = itemActivate.ptAction; ListView_HitTestEx(nmh.hwndFrom, &hitInfo); if(hitInfo.flags & LVHT_ONITEMSTATEICON) break; if(!App->sceneElement) break; XElement *sourcesElement = App->sceneElement->GetElement(TEXT("sources")); if(!sourcesElement) break; XElement *selectedElement = sourcesElement->GetElementByID(selectedID); if(!selectedElement) break; ClassInfo *curClassInfo = App->GetImageSourceClass(selectedElement->GetString(TEXT("class"))); if(curClassInfo && curClassInfo->configProc) { Vect2 multiple; ImageSource *source; App->EnableSceneSwitching(false); if(App->bRunning && App->scene) { SceneItem* selectedItem = App->scene->GetSceneItem(selectedID); source = selectedItem->GetSource(); if(source) { Vect2 curSize = Vect2(selectedElement->GetFloat(TEXT("cx"), 32.0f), selectedElement->GetFloat(TEXT("cy"), 32.0f)); Vect2 baseSize = source->GetSize(); multiple = curSize/baseSize; } } if(curClassInfo->configProc(selectedElement, false)) { if(App->bRunning) { App->EnterSceneMutex(); if(App->scene) { Vect2 newSize = Vect2(selectedElement->GetFloat(TEXT("cx"), 32.0f), selectedElement->GetFloat(TEXT("cy"), 32.0f)); newSize *= multiple; selectedElement->SetFloat(TEXT("cx"), newSize.x); selectedElement->SetFloat(TEXT("cy"), newSize.y); SceneItem* selectedItem = App->scene->GetSceneItem(selectedID); if(selectedItem->GetSource()) selectedItem->GetSource()->UpdateSettings(); selectedItem->Update(); } App->LeaveSceneMutex(); } } App->EnableSceneSwitching(true); } } break; case ID_TOGGLERECORDING: if (nmh.code == BCN_DROPDOWN) { LPNMBCDROPDOWN drop = (LPNMBCDROPDOWN)lParam; HMENU menu = CreatePopupMenu(); AppendMenu(menu, MF_STRING, 1, App->bRecordingReplayBuffer ? Str("MainWindow.StopReplayBuffer") : Str("MainWindow.StartReplayBuffer")); AppendMenu(menu, MF_STRING | (App->bRecording == !App->bRecordingReplayBuffer ? MF_DISABLED : 0), 2, App->bRecording ? Str("MainWindow.StopRecordingAndReplayBuffer") : Str("MainWindow.StartRecordingAndReplayBuffer")); POINT p; p.x = drop->rcButton.right; p.y = drop->rcButton.bottom; ClientToScreen(GetDlgItem(hwndMain, ID_TOGGLERECORDING), &p); switch (TrackPopupMenu(menu, TPM_RETURNCMD | TPM_RIGHTALIGN, p.x, p.y, 0, GetDlgItem(hwndMain, ID_TOGGLERECORDING), nullptr)) { case 1: App->RefreshStreamButtons(true); App->ToggleReplayBuffer(); App->RefreshStreamButtons(); break; case 2: App->RefreshStreamButtons(true); App->ToggleRecording(); App->ToggleReplayBuffer(); App->RefreshStreamButtons(); break; case 0: break; } } } } break; case WM_ENTERSIZEMOVE: App->bDragResize = true; break; case WM_EXITSIZEMOVE: if(App->bSizeChanging) { RECT client; GetClientRect(hwnd, &client); App->ResizeWindow(true); App->bSizeChanging = false; } App->bDragResize = false; break; case WM_DRAWITEM: if(wParam == ID_STATUS) { DRAWITEMSTRUCT &dis = *(DRAWITEMSTRUCT*)lParam; //don't dis me bro App->DrawStatusBar(dis); } break; case WM_SIZING: { RECT &screenSize = *(RECT*)lParam; int newWidth = MAX(screenSize.right - screenSize.left, minClientWidth+App->borderXSize); int newHeight = MAX(screenSize.bottom - screenSize.top , minClientHeight+App->borderYSize); /*int maxCX = GetSystemMetrics(SM_CXFULLSCREEN); int maxCY = GetSystemMetrics(SM_CYFULLSCREEN); if(newWidth > maxCX) newWidth = maxCX; if(newHeight > maxCY) newHeight = maxCY;*/ if(wParam == WMSZ_LEFT || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_TOPLEFT) screenSize.left = screenSize.right - newWidth; else screenSize.right = screenSize.left + newWidth; if(wParam == WMSZ_BOTTOM || wParam == WMSZ_BOTTOMLEFT || wParam == WMSZ_BOTTOMRIGHT) screenSize.bottom = screenSize.top + newHeight; else screenSize.top = screenSize.bottom - newHeight; return TRUE; } case WM_SIZE: { RECT client; GetClientRect(hwnd, &client); if(wParam != SIZE_MINIMIZED && (App->clientWidth != client.right || App->clientHeight != client.bottom)) { App->clientWidth = client.right; App->clientHeight = client.bottom; App->bSizeChanging = true; if(wParam == SIZE_MAXIMIZED) App->ResizeWindow(true); else App->ResizeWindow(!App->bDragResize); if(!App->bDragResize) App->bSizeChanging = false; } else if (wParam == SIZE_MINIMIZED && AppConfig->GetInt(TEXT("General"), TEXT("MinimizeToNotificationArea"), 0) != 0) { ShowWindow(hwnd, SW_HIDE); } break; } case OBS_REQUESTSTOP: if (!App->IsRunning()) break; App->Stop(); if((App->bFirstConnect && App->totalStreamTime < 30000) || !App->bAutoReconnect) { if(App->streamReport.IsValid()) { OBSMessageBox(hwndMain, App->streamReport.Array(), Str("StreamReport"), MB_ICONEXCLAMATION); App->streamReport.Clear(); } else OBSMessageBox(hwnd, Str("Connection.Disconnected"), NULL, MB_ICONEXCLAMATION); } else if(wParam == 0) { //FIXME: show some kind of non-modal notice to the user explaining why they got disconnected //status bar would be nice, but that only renders when we're streaming. PlaySound((LPCTSTR)SND_ALIAS_SYSTEMASTERISK, NULL, SND_ALIAS_ID | SND_ASYNC); if (!App->reconnectTimeout) { //fire immediately PostMessage(hwndMain, OBS_RECONNECT, 0, 0); } else { App->bReconnecting = false; OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_RECONNECTING), hwnd, OBS::ReconnectDialogProc); } } break; case OBS_NETWORK_FAILED: if ((App->bFirstConnect && App->totalStreamTime < 30000) || !App->bAutoReconnect) { //no reconnect, or the connection died very early in the stream App->Stop(); if (App->streamReport.IsValid()) { OBSMessageBox(hwndMain, App->streamReport.Array(), Str("StreamReport"), MB_ICONEXCLAMATION); App->streamReport.Clear(); } else OBSMessageBox(hwnd, Str("Connection.Disconnected"), NULL, MB_ICONEXCLAMATION); } else { PlaySound((LPCTSTR)SND_ALIAS_SYSTEMASTERISK, NULL, SND_ALIAS_ID | SND_ASYNC); if (!App->reconnectTimeout) { //fire immediately App->RestartNetwork(); } else { OBSDialogBox(hinstMain, MAKEINTRESOURCE(IDD_RECONNECTING), hwnd, OBS::ReconnectDialogProc); } } break; case OBS_CALLHOTKEY: App->CallHotkey((DWORD)lParam, wParam != 0); break; case OBS_RECONNECT: App->bReconnecting = true; App->Start(); break; case OBS_SETSCENE: { TSTR lpScene = (TSTR)lParam; App->SetScene(lpScene); Free(lpScene); break; } case OBS_SETSOURCEORDER: { StringList *order = (StringList*)lParam; App->SetSourceOrder(*order); delete(order); break; } case OBS_SETSOURCERENDER: { bool render = lParam > 0; CTSTR sourceName = (CTSTR) wParam; App->SetSourceRender(sourceName, render); Free((void *)sourceName); break; } case OBS_UPDATESTATUSBAR: App->SetStatusBarData(); break; case OBS_NOTIFICATIONAREA: // the point is to only perform the show/hide (minimize) or the menu creation if no modal dialogs are opened // if a modal dialog is topmost, then simply focus it switch (lParam) { case WM_LBUTTONUP: if (IsWindowEnabled(hwnd)) PostMessage(hwnd, WM_COMMAND, MAKEWPARAM(ID_MINIMIZERESTORE, 0), 0); else SetForegroundWindow(GetWindow(hwnd, GW_ENABLEDPOPUP)); break; case WM_RBUTTONUP: if (IsWindowEnabled(hwnd)) { HMENU hMenu = CreatePopupMenu(); if (AppConfig->GetInt(TEXT("General"), TEXT("MinimizeToNotificationArea"), 0) != 0) { AppendMenu(hMenu, MF_STRING, ID_MINIMIZERESTORE, IsWindowVisible(hwnd) ? Str("Settings.General.HideObs") : Str("Settings.General.ShowObs")); AppendMenu(hMenu, MF_SEPARATOR, 0, 0); } AddProfilesToMenu(hMenu); AppendMenu(hMenu, MF_SEPARATOR, 0, 0); if (App->bRunning) { if (App->bTestStream) AppendMenu(hMenu, MF_STRING, ID_TESTSTREAM, Str("MainWindow.StopTest")); else AppendMenu(hMenu, MF_STRING, ID_STARTSTOP, Str("MainWindow.StopStream")); } else AppendMenu(hMenu, MF_STRING, ID_STARTSTOP, Str("MainWindow.StartStream")); if (!IsIconic(hwnd)) AppendMenu(hMenu, MF_STRING | (App->bFullscreenMode ? MF_CHECKED : 0), ID_FULLSCREENMODE, Str("MainMenu.Settings.FullscreenMode")); AppendMenu(hMenu, MF_STRING | (App->bAlwaysOnTop ? MF_CHECKED : 0), ID_ALWAYSONTOP, Str("MainMenu.Settings.AlwaysOnTop")); AppendMenu(hMenu, MF_STRING, ID_FILE_EXIT, Str("MainWindow.Exit")); SetForegroundWindow(hwnd); POINT p; GetCursorPos(&p); TrackPopupMenu(hMenu, TPM_LEFTALIGN, p.x, p.y, 0, hwnd, NULL); DestroyMenu(hMenu); } else SetForegroundWindow(GetWindow(hwnd, GW_ENABLEDPOPUP)); break; } break; case WM_CLOSE: PostQuitMessage(0); break; default: if (App && message == App->wmExplorerRestarted) { if(App->bNotificationAreaIcon) { App->bNotificationAreaIcon = false; App->ShowNotificationAreaIcon(); } } return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } ItemModifyType GetItemModifyType(const Vect2 &mousePos, const Vect2 &itemPos, const Vect2 &itemSize, const Vect4 &crop, const Vect2 &scaleVal) { Vect2 lowerRight = itemPos+itemSize; float epsilon = 10.0f; Vect2 croppedItemPos = itemPos + Vect2(crop.x / scaleVal.x, crop.y / scaleVal.y); Vect2 croppedLowerRight = lowerRight - Vect2(crop.w / scaleVal.x, crop.z / scaleVal.y); if( mousePos.x < croppedItemPos.x || mousePos.y < croppedItemPos.y || mousePos.x > croppedLowerRight.x || mousePos.y > croppedLowerRight.y ) { return ItemModifyType_None; } // Corner sizing if(mousePos.CloseTo(croppedItemPos, epsilon)) return ItemModifyType_ScaleTopLeft; else if(mousePos.CloseTo(croppedLowerRight, epsilon)) return ItemModifyType_ScaleBottomRight; else if(mousePos.CloseTo(Vect2(croppedLowerRight.x, croppedItemPos.y), epsilon)) return ItemModifyType_ScaleTopRight; else if(mousePos.CloseTo(Vect2(croppedItemPos.x, croppedLowerRight.y), epsilon)) return ItemModifyType_ScaleBottomLeft; epsilon = 4.0f; // Edge sizing if(CloseFloat(mousePos.x, croppedItemPos.x, epsilon)) return ItemModifyType_ScaleLeft; else if(CloseFloat(mousePos.x, croppedLowerRight.x, epsilon)) return ItemModifyType_ScaleRight; else if(CloseFloat(mousePos.y, croppedItemPos.y, epsilon)) return ItemModifyType_ScaleTop; else if(CloseFloat(mousePos.y, croppedLowerRight.y, epsilon)) return ItemModifyType_ScaleBottom; return ItemModifyType_Move; } /** * Maps a point in window coordinates to frame coordinates. */ Vect2 OBS::MapWindowToFramePos(Vect2 mousePos) { if(App->renderFrameIn1To1Mode) return (mousePos - App->GetRenderFrameOffset()) * (App->GetBaseSize() / App->GetOutputSize()); return (mousePos - App->GetRenderFrameOffset()) * (App->GetBaseSize() / App->GetRenderFrameSize()); } /** * Maps a point in frame coordinates to window coordinates. */ Vect2 OBS::MapFrameToWindowPos(Vect2 framePos) { if(App->renderFrameIn1To1Mode) return framePos * (App->GetOutputSize() / App->GetBaseSize()) + App->GetRenderFrameOffset(); return framePos * (App->GetRenderFrameSize() / App->GetBaseSize()) + App->GetRenderFrameOffset(); } /** * Maps a size in window coordinates to frame coordinates. */ Vect2 OBS::MapWindowToFrameSize(Vect2 windowSize) { if(App->renderFrameIn1To1Mode) return windowSize * (App->GetBaseSize() / App->GetOutputSize()); return windowSize * (App->GetBaseSize() / App->GetRenderFrameSize()); } /** * Maps a size in frame coordinates to window coordinates. */ Vect2 OBS::MapFrameToWindowSize(Vect2 frameSize) { if(App->renderFrameIn1To1Mode) return frameSize * (App->GetOutputSize() / App->GetBaseSize()); return frameSize * (App->GetRenderFrameSize() / App->GetBaseSize()); } /** * Returns the scale of the window relative to the actual frame size. E.g. * if the window is twice the size of the frame this will return "0.5". */ Vect2 OBS::GetWindowToFrameScale() { return MapWindowToFrameSize(Vect2(1.0f, 1.0f)); } /** * Returns the scale of the frame relative to the window size. E.g. * if the window is twice the size of the frame this will return "2.0". */ Vect2 OBS::GetFrameToWindowScale() { return MapFrameToWindowSize(Vect2(1.0f, 1.0f)); } bool OBS::EnsureCropValid(SceneItem *&scaleItem, Vect2 &minSize, Vect2 &snapSize, bool bControlDown, int cropEdges, bool cropSymmetric) { Vect2 scale = (scaleItem->GetSource() ? scaleItem->GetSource()->GetSize() : scaleItem->GetSize()) / scaleItem->GetSize(); // When keep aspect is on, cropping can only be half size - 2 * minsize if (cropSymmetric) { if (cropEdges & (edgeLeft | edgeRight)) { if (scaleItem->GetCrop().x > (scaleItem->size.x / 2 ) - 2 * minSize.x) { scaleItem->crop.x = ((scaleItem->size.x / 2 ) - 2 * minSize.x) * scale.x; scaleItem->crop.w = ((scaleItem->size.x / 2 ) - 2 * minSize.x) * scale.x; } scaleItem->crop.x = (scaleItem->crop.x < 0.0f) ? 0.0f : scaleItem->crop.x; scaleItem->crop.w = (scaleItem->crop.w < 0.0f) ? 0.0f : scaleItem->crop.w; } if (cropEdges & (edgeTop | edgeBottom)) { if (scaleItem->GetCrop().y > (scaleItem->size.y / 2 ) - 2 * minSize.y) { scaleItem->crop.y = ((scaleItem->size.y / 2 ) - 2 * minSize.y) * scale.y; scaleItem->crop.z = ((scaleItem->size.y / 2 ) - 2 * minSize.y) * scale.y; } scaleItem->crop.y = (scaleItem->crop.y < 0.0f) ? 0.0f : scaleItem->crop.y; scaleItem->crop.z = (scaleItem->crop.z < 0.0f) ? 0.0f : scaleItem->crop.z; } } else { // left if (scaleItem->GetCrop().x > (scaleItem->size.x - scaleItem->GetCrop().w - 32) - minSize.x && cropEdges & edgeLeft) { scaleItem->crop.x = ((scaleItem->size.x - scaleItem->GetCrop().w - 32) - minSize.x) * scale.x; } scaleItem->crop.x = (scaleItem->crop.x < 0.0f) ? 0.0f : scaleItem->crop.x; // top if (scaleItem->GetCrop().y > (scaleItem->size.y - scaleItem->GetCrop().z - 32) - minSize.y && cropEdges & edgeTop) { scaleItem->crop.y = ((scaleItem->size.y - scaleItem->GetCrop().z - 32) - minSize.y) * scale.y; } scaleItem->crop.y = (scaleItem->crop.y < 0.0f) ? 0.0f : scaleItem->crop.y; // right if (scaleItem->GetCrop().w > (scaleItem->size.x - scaleItem->GetCrop().x - 32) - minSize.x && cropEdges & edgeRight) { scaleItem->crop.w = ((scaleItem->size.x - scaleItem->GetCrop().x - 32) - minSize.x) * scale.x; } scaleItem->crop.w = (scaleItem->crop.w < 0.0f) ? 0.0f : scaleItem->crop.w; // bottom if (scaleItem->GetCrop().z > (scaleItem->size.y - scaleItem->GetCrop().y - 32) - minSize.y && cropEdges & edgeBottom) { scaleItem->crop.z = ((scaleItem->size.y - scaleItem->GetCrop().y - 32) - minSize.y) * scale.y; } scaleItem->crop.z = (scaleItem->crop.z < 0.0f) ? 0.0f : scaleItem->crop.z; } if (!bControlDown) { // left if(CloseFloat(scaleItem->GetCrop().x, 0.0f, snapSize.x)) { scaleItem->crop.x = 0.0f; } // top if(CloseFloat(scaleItem->GetCrop().y, 0.0f, snapSize.y)) { scaleItem->crop.y = 0.0f; } // right if(CloseFloat(scaleItem->GetCrop().w, 0.0f, snapSize.x)) { scaleItem->crop.w = 0.0f; } // bottom if(CloseFloat(scaleItem->GetCrop().z, 0.0f, snapSize.y)) { scaleItem->crop.z = 0.0f; } } return true; } LRESULT CALLBACK OBS::ProjectorFrameProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_KEYDOWN: if (wParam == VK_ESCAPE) App->bPleaseDisableProjector = true; break; case WM_CLOSE: App->bPleaseDisableProjector = true; break; case WM_SETCURSOR: if (App->bEnableProjectorCursor) return DefWindowProc(hwnd, message, wParam, lParam); else SetCursor(NULL); break; default: return DefWindowProc(hwnd, message, wParam, lParam); } return 0; } LRESULT CALLBACK OBS::RenderFrameProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES); if(message == WM_ERASEBKGND) { if(App->bRenderViewEnabled && App->bRunning) return 1L; else return DefWindowProc(hwnd, message, wParam, lParam); } else if(message == WM_LBUTTONDOWN || message == WM_RBUTTONDOWN) { POINTS pos; pos.x = (short)LOWORD(lParam); pos.y = (short)HIWORD(lParam); if((App->bEditMode || message == WM_RBUTTONDOWN) && App->scene) { Vect2 mousePos = Vect2(float(pos.x), float(pos.y)); Vect2 framePos = MapWindowToFramePos(mousePos); bool bControlDown = HIBYTE(GetKeyState(VK_LCONTROL)) != 0 || HIBYTE(GetKeyState(VK_RCONTROL)) != 0; SetFocus(hwnd); List items; App->scene->GetSelectedItems(items); if(!items.Num()) { App->scene->GetItemsOnPoint(framePos, items); if(items.Num()) { SceneItem *topItem = items.Last(); App->bItemWasSelected = topItem->bSelected; if(!bControlDown) { /* clears all selections */ App->bChangingSources = true; ListView_SetItemState(hwndSources, -1, 0, LVIS_SELECTED | LVIS_FOCUSED); App->bChangingSources = false; App->scene->DeselectAll(); } topItem->Select(true); App->bChangingSources = true; ListView_SetItemState(hwndSources, topItem->GetID(), LVIS_SELECTED, LVIS_SELECTED); App->bChangingSources = false; if (App->bEditMode) if(App->modifyType == ItemModifyType_None) App->modifyType = ItemModifyType_Move; } else if(!bControlDown) //clicked on empty space without control { /* clears all selections */ App->bChangingSources = true; ListView_SetItemState(hwndSources, -1, 0, LVIS_SELECTED | LVIS_FOCUSED); App->bChangingSources = false; App->scene->DeselectAll(); } } else { App->scene->GetItemsOnPoint(framePos, items); if(items.Num()) { SceneItem *topItem = items.Last(); App->bItemWasSelected = topItem->bSelected; } } if (message == WM_LBUTTONDOWN) { App->bMouseDown = true; App->lastMousePos = App->startMousePos = mousePos; App->scene->GetSelectedItems(items); for(UINT i=0; istartPos = items[i]->pos; items[i]->startSize = items[i]->size; } } else App->bRMouseDown = true; } else { //SendMessage(hwndMain, WM_NCLBUTTONDOWN, HTCAPTION, (LPARAM)&pos); return 0; } } else if(message == WM_MOUSEMOVE) { if(App->bEditMode && App->scene) { POINTS pos; pos.x = (short)LOWORD(lParam); pos.y = (short)HIWORD(lParam); Vect2 mousePos = Vect2(float(pos.x), float(pos.y)); Vect2 scaleVal = GetWindowToFrameScale(); SceneItem *&scaleItem = App->scaleItem; //just reduces a bit of typing if(!App->bMouseDown) { List items; App->scene->GetSelectedItems(items); App->scaleItem = NULL; App->modifyType = ItemModifyType_None; for(int i=int(items.Num()-1); i>=0; i--) { SceneItem *item = items[i]; // Get item in window coordinates Vect2 adjPos = MapFrameToWindowPos(item->GetPos()); Vect2 adjSize = MapFrameToWindowSize(item->GetSize()); Vect2 adjSizeBase = MapFrameToWindowSize(item->GetSource() ? item->GetSource()->GetSize() : item->GetSize()); ItemModifyType curType = GetItemModifyType(mousePos, adjPos, adjSize, item->GetCrop(), scaleVal); if(curType > ItemModifyType_Move) { App->modifyType = curType; scaleItem = item; break; } else if(curType == ItemModifyType_Move) App->modifyType = ItemModifyType_Move; } } else { Vect2 baseRenderSize = App->GetBaseSize(); Vect2 framePos = MapWindowToFramePos(mousePos); if(!App->bMouseMoved && mousePos.Dist(App->startMousePos) > 2.0f) { SetCapture(hwnd); App->bMouseMoved = true; } if(App->bMouseMoved) { bool bControlDown = HIBYTE(GetKeyState(VK_LCONTROL)) != 0 || HIBYTE(GetKeyState(VK_RCONTROL)) != 0; bool bKeepAspect = HIBYTE(GetKeyState(VK_LSHIFT)) == 0 && HIBYTE(GetKeyState(VK_RSHIFT)) == 0; bool bCropSymmetric = bKeepAspect; bool isCropping = HIBYTE(GetKeyState(VK_MENU)) != 0; if (isCropping) { switch(App->modifyType) { case ItemModifyType_ScaleTop: App->modifyType = ItemModifyType_CropTop; break; case ItemModifyType_ScaleLeft: App->modifyType = ItemModifyType_CropLeft; break; case ItemModifyType_ScaleRight: App->modifyType = ItemModifyType_CropRight; break; case ItemModifyType_ScaleBottom: App->modifyType = ItemModifyType_CropBottom; break; case ItemModifyType_ScaleTopLeft: App->modifyType = ItemModifyType_CropTopLeft; break; case ItemModifyType_ScaleTopRight: App->modifyType = ItemModifyType_CropTopRight; break; case ItemModifyType_ScaleBottomRight: App->modifyType = ItemModifyType_CropBottomRight; break; case ItemModifyType_ScaleBottomLeft: App->modifyType = ItemModifyType_CropBottomLeft; break; } } List items; App->scene->GetSelectedItems(items); Vect2 totalAdjust = (mousePos-App->startMousePos)*scaleVal; Vect2 frameStartMousePos = App->MapWindowToFramePos(App->startMousePos); Vect2 minSize = scaleVal*21.0f; Vect2 snapSize = scaleVal*10.0f; Vect2 baseScale; float baseScaleAspect; Vect2 cropFactor; if(scaleItem) { if(scaleItem->GetSource()) { baseScale = scaleItem->GetSource()->GetSize(); } else { bKeepAspect = false; //if the source is missing (due to invalid setting or missing plugin), don't allow aspect lock baseScale = scaleItem->size; } baseScaleAspect = baseScale.x/baseScale.y; cropFactor = baseScale / scaleItem->GetSize(); } int edgeAll = edgeLeft | edgeRight | edgeBottom | edgeTop; //more clusterf*** action, straight to your doorstep. does moving, scaling, snapping, as well as keeping aspect ratio with shift down switch(App->modifyType) { case ItemModifyType_Move: { bool bFoundSnap = false; for(UINT i=0; ipos = item->startPos+totalAdjust; if(!bControlDown) { Vect2 pos = item->pos; Vect2 bottomRight = pos+item->size; pos += item->GetCropTL(); bottomRight += item->GetCropBR(); bool bVerticalSnap = true; if(CloseFloat(pos.x, 0.0f, snapSize.x)) item->pos.x = -item->GetCrop().x; else if(CloseFloat(bottomRight.x, baseRenderSize.x, snapSize.x)) item->pos.x = baseRenderSize.x-item->size.x+item->GetCrop().w; else bVerticalSnap = false; bool bHorizontalSnap = true; if(CloseFloat(pos.y, 0.0f, snapSize.y)) item->pos.y = -item->GetCrop().y; else if(CloseFloat(bottomRight.y, baseRenderSize.y, snapSize.y)) item->pos.y = baseRenderSize.y-item->size.y+item->GetCrop().z; else bHorizontalSnap = true; if(bVerticalSnap || bHorizontalSnap) { bFoundSnap = true; totalAdjust = item->pos-item->startPos; } } } if(bFoundSnap) { for(UINT i=0; ipos = item->startPos+totalAdjust; } } break; } case ItemModifyType_CropTop: if (!scaleItem) break; scaleItem->crop.y = ((frameStartMousePos.y - scaleItem->pos.y) + totalAdjust.y) * cropFactor.y; if (!bCropSymmetric) scaleItem->crop.z = ((frameStartMousePos.y - scaleItem->pos.y) + totalAdjust.y) * cropFactor.y; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeTop | (!bCropSymmetric ? edgeBottom : 0), !bCropSymmetric); break; case ItemModifyType_CropBottom: if (!scaleItem) break; scaleItem->crop.z = ((scaleItem->pos.y + scaleItem->size.y - frameStartMousePos.y) - totalAdjust.y) * cropFactor.y; if (!bCropSymmetric) scaleItem->crop.y = ((scaleItem->pos.y + scaleItem->size.y - frameStartMousePos.y) - totalAdjust.y) * cropFactor.y; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeBottom | (!bCropSymmetric ? edgeTop : 0), !bCropSymmetric); break; case ItemModifyType_CropLeft: if (!scaleItem) break; scaleItem->crop.x = ((frameStartMousePos.x - scaleItem->pos.x) + totalAdjust.x) * cropFactor.x; if (!bCropSymmetric) scaleItem->crop.w = ((frameStartMousePos.x - scaleItem->pos.x) + totalAdjust.x) * cropFactor.x; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeLeft | (!bCropSymmetric ? edgeRight : 0), !bCropSymmetric); break; case ItemModifyType_CropRight: if (!scaleItem) break; scaleItem->crop.w = ((scaleItem->pos.x + scaleItem->size.x - frameStartMousePos.x) - totalAdjust.x) * cropFactor.x; if (!bCropSymmetric) scaleItem->crop.x = ((scaleItem->pos.x + scaleItem->size.x - frameStartMousePos.x) - totalAdjust.x) * cropFactor.x; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeRight | (!bCropSymmetric ? edgeLeft : 0), !bCropSymmetric); break; case ItemModifyType_CropBottomLeft: if (!scaleItem) break; if (bCropSymmetric) { scaleItem->crop.z = ((scaleItem->pos.y + scaleItem->size.y - frameStartMousePos.y) - totalAdjust.y) * cropFactor.y; scaleItem->crop.x = ((frameStartMousePos.x - scaleItem->pos.x) + totalAdjust.x) * cropFactor.x; } else { float amount = MIN(((scaleItem->pos.y + scaleItem->size.y - frameStartMousePos.y) - totalAdjust.y), ((frameStartMousePos.x - scaleItem->pos.x) + totalAdjust.x)); scaleItem->crop.w = amount * cropFactor.x; scaleItem->crop.x = amount * cropFactor.x; scaleItem->crop.y = amount * cropFactor.y; scaleItem->crop.z = amount * cropFactor.y; } EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeLeft | edgeBottom | (!bCropSymmetric ? edgeAll : 0), !bCropSymmetric); break; case ItemModifyType_CropBottomRight: if (!scaleItem) break; if (bCropSymmetric) { scaleItem->crop.z = ((scaleItem->pos.y + scaleItem->size.y - frameStartMousePos.y) - totalAdjust.y) * cropFactor.y; scaleItem->crop.w = ((scaleItem->pos.x + scaleItem->size.x - frameStartMousePos.x) - totalAdjust.x) * cropFactor.x; } else { float amount = MIN(((scaleItem->pos.y + scaleItem->size.y - frameStartMousePos.y) - totalAdjust.y), ((scaleItem->pos.x + scaleItem->size.x - frameStartMousePos.x) - totalAdjust.x)); scaleItem->crop.w = amount * cropFactor.x; scaleItem->crop.x = amount * cropFactor.x; scaleItem->crop.y = amount * cropFactor.y; scaleItem->crop.z = amount * cropFactor.y; } EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeRight | edgeBottom | (!bCropSymmetric ? edgeAll : 0), !bCropSymmetric); break; case ItemModifyType_CropTopLeft: if (!scaleItem) break; if (bCropSymmetric) { scaleItem->crop.y = ((frameStartMousePos.y - scaleItem->pos.y) + totalAdjust.y) * cropFactor.y; scaleItem->crop.x = ((frameStartMousePos.x - scaleItem->pos.x) + totalAdjust.x) * cropFactor.x; } else { float amount = MIN(((frameStartMousePos.y - scaleItem->pos.y) + totalAdjust.y), ((frameStartMousePos.x - scaleItem->pos.x) + totalAdjust.x)); scaleItem->crop.w = amount * cropFactor.x; scaleItem->crop.x = amount * cropFactor.x; scaleItem->crop.y = amount * cropFactor.y; scaleItem->crop.z = amount * cropFactor.y; } EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeLeft | edgeTop | (!bCropSymmetric ? edgeAll : 0), !bCropSymmetric); break; case ItemModifyType_CropTopRight: if (!scaleItem) break; if (bCropSymmetric) { scaleItem->crop.y = ((frameStartMousePos.y - scaleItem->pos.y) + totalAdjust.y) * cropFactor.y; scaleItem->crop.w = ((scaleItem->pos.x + scaleItem->size.x - frameStartMousePos.x) - totalAdjust.x) * cropFactor.x; } else { float amount = MIN(((frameStartMousePos.y - scaleItem->pos.y) + totalAdjust.y), ((scaleItem->pos.x + scaleItem->size.x - frameStartMousePos.x) - totalAdjust.x)); scaleItem->crop.w = amount * cropFactor.x; scaleItem->crop.x = amount * cropFactor.x; scaleItem->crop.y = amount * cropFactor.y; scaleItem->crop.z = amount * cropFactor.y; } EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeRight | edgeTop | (!bCropSymmetric ? edgeAll : 0), !bCropSymmetric); break; case ItemModifyType_ScaleBottom: { if (!scaleItem) break; Vect2 pos = scaleItem->pos + scaleItem->GetCropTL(); scaleItem->size.y = scaleItem->startSize.y+totalAdjust.y; if(scaleItem->size.y < minSize.y) scaleItem->size.y = minSize.y; if(!bControlDown) { float bottom = scaleItem->pos.y+scaleItem->size.y-scaleItem->GetCrop().z; if(CloseFloat(bottom, baseRenderSize.y, snapSize.y)) { bottom = baseRenderSize.y; scaleItem->size.y = bottom-scaleItem->pos.y+scaleItem->GetCrop().z; } } if(bKeepAspect) scaleItem->size.x = scaleItem->size.y*baseScaleAspect; else scaleItem->size.x = scaleItem->startSize.x; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeAll, false); scaleItem->pos = pos - scaleItem->GetCropTL(); } break; case ItemModifyType_ScaleTop: { if (!scaleItem) break; Vect2 pos = scaleItem->pos + scaleItem->size + scaleItem->GetCropBR(); scaleItem->size.y = scaleItem->startSize.y-totalAdjust.y; if(scaleItem->size.y < minSize.y) scaleItem->size.y = minSize.y; if(!bControlDown) { float top = scaleItem->startPos.y+(scaleItem->startSize.y-scaleItem->size.y)+scaleItem->GetCrop().x; if(CloseFloat(top, 0.0f, snapSize.y)) scaleItem->size.y = scaleItem->startPos.y+scaleItem->startSize.y+scaleItem->GetCrop().x; } if(bKeepAspect) scaleItem->size.x = scaleItem->size.y*baseScaleAspect; else scaleItem->size.x = scaleItem->startSize.x; totalAdjust.y = scaleItem->startSize.y-scaleItem->size.y; scaleItem->pos.y = scaleItem->startPos.y+totalAdjust.y; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeAll, false); scaleItem->pos = pos - scaleItem->GetCropBR() - scaleItem->size; } break; case ItemModifyType_ScaleRight: { if (!scaleItem) break; Vect2 pos = scaleItem->pos + scaleItem->GetCropTL(); scaleItem->size.x = scaleItem->startSize.x+totalAdjust.x; if(scaleItem->size.x < minSize.x) scaleItem->size.x = minSize.x; if(!bControlDown) { float right = scaleItem->pos.x + scaleItem->size.x-scaleItem->GetCrop().y; if(CloseFloat(right, baseRenderSize.x, snapSize.x)) { right = baseRenderSize.x; scaleItem->size.x = right-scaleItem->pos.x+scaleItem->GetCrop().y; } } if(bKeepAspect) scaleItem->size.y = scaleItem->size.x/baseScaleAspect; else scaleItem->size.y = scaleItem->startSize.y; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeAll, false); scaleItem->pos = pos - scaleItem->GetCropTL(); } break; case ItemModifyType_ScaleLeft: { if (!scaleItem) break; Vect2 pos = scaleItem->pos + scaleItem->size + scaleItem->GetCropBR(); scaleItem->size.x = scaleItem->startSize.x-totalAdjust.x; if(scaleItem->size.x < minSize.x) scaleItem->size.x = minSize.x; if(!bControlDown) { float left = scaleItem->startPos.x+(scaleItem->startSize.x-scaleItem->size.x)+scaleItem->GetCrop().w; if(CloseFloat(left, 0.0f, snapSize.x)) scaleItem->size.x = scaleItem->startPos.x+scaleItem->startSize.x+scaleItem->GetCrop().w; } if(bKeepAspect) scaleItem->size.y = scaleItem->size.x/baseScaleAspect; else scaleItem->size.y = scaleItem->startSize.y; totalAdjust.x = scaleItem->startSize.x-scaleItem->size.x; scaleItem->pos.x = scaleItem->startPos.x+totalAdjust.x; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeAll, false); scaleItem->pos = pos - scaleItem->GetCropBR() - scaleItem->size; } break; case ItemModifyType_ScaleBottomRight: { if (!scaleItem) break; Vect2 pos = scaleItem->pos + scaleItem->GetCropTL(); scaleItem->size = scaleItem->startSize+totalAdjust; scaleItem->size.ClampMin(minSize); if(!bControlDown) { Vect2 cropPart = Vect2(scaleItem->GetCrop().y, scaleItem->GetCrop().z); Vect2 lowerRight = scaleItem->pos+scaleItem->size-cropPart; if(CloseFloat(lowerRight.x, baseRenderSize.x, snapSize.x)) { lowerRight.x = baseRenderSize.x; scaleItem->size.x = lowerRight.x-scaleItem->pos.x+cropPart.x; } if(CloseFloat(lowerRight.y, baseRenderSize.y, snapSize.y)) { lowerRight.y = baseRenderSize.y; scaleItem->size.y = lowerRight.y-scaleItem->pos.y+cropPart.y; } } if(bKeepAspect) { float scaleAspect = scaleItem->size.x/scaleItem->size.y; if(scaleAspect < baseScaleAspect) scaleItem->size.x = scaleItem->size.y*baseScaleAspect; else if(scaleAspect > baseScaleAspect) scaleItem->size.y = scaleItem->size.x/baseScaleAspect; } EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeAll, false); scaleItem->pos = pos - scaleItem->GetCropTL(); } break; case ItemModifyType_ScaleTopLeft: { if (!scaleItem) break; Vect2 pos = scaleItem->pos + scaleItem->size + scaleItem->GetCropBR(); scaleItem->size = scaleItem->startSize-totalAdjust; scaleItem->size.ClampMin(minSize); if(!bControlDown) { Vect2 cropPart = Vect2(scaleItem->crop.w, scaleItem->crop.x); Vect2 topLeft = scaleItem->startPos+(scaleItem->startSize-scaleItem->size)+cropPart; if(CloseFloat(topLeft.x, 0.0f, snapSize.x)) scaleItem->size.x = scaleItem->startPos.x+scaleItem->startSize.x+cropPart.x; if(CloseFloat(topLeft.y, 0.0f, snapSize.y)) scaleItem->size.y = scaleItem->startPos.y+scaleItem->startSize.y+cropPart.y; } if(bKeepAspect) { float scaleAspect = scaleItem->size.x/scaleItem->size.y; if(scaleAspect < baseScaleAspect) scaleItem->size.x = scaleItem->size.y*baseScaleAspect; else if(scaleAspect > baseScaleAspect) scaleItem->size.y = scaleItem->size.x/baseScaleAspect; } totalAdjust = scaleItem->startSize-scaleItem->size; scaleItem->pos = scaleItem->startPos+totalAdjust; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeAll, false); scaleItem->pos = pos - scaleItem->GetCropBR() - scaleItem->size; } break; case ItemModifyType_ScaleBottomLeft: { if (!scaleItem) break; Vect2 pos = scaleItem->pos + Vect2(scaleItem->size.x, 0) + scaleItem->GetCropTR(); scaleItem->size.x = scaleItem->startSize.x-totalAdjust.x; scaleItem->size.y = scaleItem->startSize.y+totalAdjust.y; scaleItem->size.ClampMin(minSize); if(!bControlDown) { Vect2 cropPart = Vect2(scaleItem->GetCrop().w, scaleItem->GetCrop().z); float left = scaleItem->startPos.x+(scaleItem->startSize.x-scaleItem->size.x)+cropPart.x; float bottom = scaleItem->pos.y+scaleItem->size.y-cropPart.y; if(CloseFloat(left, 0.0f, snapSize.x)) scaleItem->size.x = scaleItem->startPos.x+scaleItem->startSize.x+cropPart.x; if(CloseFloat(bottom, baseRenderSize.y, snapSize.y)) { bottom = baseRenderSize.y; scaleItem->size.y = bottom-scaleItem->pos.y+cropPart.y; } } if(bKeepAspect) { float scaleAspect = scaleItem->size.x/scaleItem->size.y; if(scaleAspect < baseScaleAspect) scaleItem->size.x = scaleItem->size.y*baseScaleAspect; else if(scaleAspect > baseScaleAspect) scaleItem->size.y = scaleItem->size.x/baseScaleAspect; } totalAdjust.x = scaleItem->startSize.x-scaleItem->size.x; scaleItem->pos.x = scaleItem->startPos.x+totalAdjust.x; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeAll, false); scaleItem->pos = pos - scaleItem->GetCropTR() - Vect2(scaleItem->size.x, 0); } break; case ItemModifyType_ScaleTopRight: { if (!scaleItem) break; Vect2 pos = scaleItem->pos + Vect2(0, scaleItem->size.y) + scaleItem->GetCropBL(); scaleItem->size.x = scaleItem->startSize.x+totalAdjust.x; scaleItem->size.y = scaleItem->startSize.y-totalAdjust.y; scaleItem->size.ClampMin(minSize); if(!bControlDown) { Vect2 cropPart = Vect2(scaleItem->GetCrop().y, scaleItem->GetCrop().x); float right = scaleItem->pos.x+scaleItem->size.x-cropPart.x; float top = scaleItem->startPos.y+(scaleItem->startSize.y-scaleItem->size.y)+cropPart.y; if(CloseFloat(right, baseRenderSize.x, snapSize.x)) { right = baseRenderSize.x; scaleItem->size.x = right-scaleItem->pos.x+cropPart.x; } if(CloseFloat(top, 0.0f, snapSize.y)) scaleItem->size.y = scaleItem->startPos.y+scaleItem->startSize.y+cropPart.y; } if(bKeepAspect) { float scaleAspect = scaleItem->size.x/scaleItem->size.y; if(scaleAspect < baseScaleAspect) scaleItem->size.x = scaleItem->size.y*baseScaleAspect; else if(scaleAspect > baseScaleAspect) scaleItem->size.y = scaleItem->size.x/baseScaleAspect; } totalAdjust.y = scaleItem->startSize.y-scaleItem->size.y; scaleItem->pos.y = scaleItem->startPos.y+totalAdjust.y; EnsureCropValid(scaleItem, minSize, snapSize, bControlDown, edgeAll, false); scaleItem->pos = pos - scaleItem->GetCropBL() - Vect2(0, scaleItem->size.y); } break; } App->lastMousePos = mousePos; } } switch(App->modifyType) { case ItemModifyType_ScaleBottomLeft: case ItemModifyType_ScaleTopRight: SetCursor(LoadCursor(NULL, IDC_SIZENESW)); return 0; case ItemModifyType_ScaleBottomRight: case ItemModifyType_ScaleTopLeft: SetCursor(LoadCursor(NULL, IDC_SIZENWSE)); return 0; case ItemModifyType_CropLeft: case ItemModifyType_CropRight: case ItemModifyType_ScaleLeft: case ItemModifyType_ScaleRight: SetCursor(LoadCursor(NULL, IDC_SIZEWE)); return 0; case ItemModifyType_CropTop: case ItemModifyType_CropBottom: case ItemModifyType_ScaleTop: case ItemModifyType_ScaleBottom: SetCursor(LoadCursor(NULL, IDC_SIZENS)); return 0; default: SetCursor(LoadCursor(NULL, IDC_ARROW)); return 0; } } } else if(message == WM_LBUTTONUP || message == WM_RBUTTONUP) { if((App->bEditMode || message == WM_RBUTTONUP) && App->scene) { POINTS pos; pos.x = (short)LOWORD(lParam); pos.y = (short)HIWORD(lParam); if(App->bMouseDown || App->bRMouseDown) { Vect2 mousePos = Vect2(float(pos.x), float(pos.y)); bool bControlDown = HIBYTE(GetKeyState(VK_CONTROL)) != 0; List items; if(!App->bMouseMoved) { Vect2 framePos = MapWindowToFramePos(mousePos); App->scene->GetItemsOnPoint(framePos, items); if(bControlDown && App->bItemWasSelected) { SceneItem *lastItem = items.Last(); lastItem->Select(false); ListView_SetItemState(hwndSources, lastItem->GetID(), 0, LVIS_SELECTED); } else { if(items.Num()) { SceneItem *topItem = items.Last(); if(!bControlDown) { App->bChangingSources = true; ListView_SetItemState(hwndSources, -1, 0, LVIS_SELECTED | LVIS_FOCUSED); App->bChangingSources = false; App->scene->DeselectAll(); } topItem->Select(true); App->bChangingSources = true; SetFocus(hwnd); ListView_SetItemState(hwndSources, topItem->GetID(), LVIS_SELECTED, LVIS_SELECTED); App->bChangingSources = false; if(App->modifyType == ItemModifyType_None) App->modifyType = ItemModifyType_Move; } else if(!bControlDown) //clicked on empty space without control { App->bChangingSources = true; ListView_SetItemState(hwndSources, -1, 0, LVIS_SELECTED | LVIS_FOCUSED); App->bChangingSources = false; App->scene->DeselectAll(); } } } else if(message == WM_LBUTTONUP) { App->scene->GetSelectedItems(items); ReleaseCapture(); App->bMouseMoved = false; for(UINT i=0; ipos += 0.5f).Floor(); (item->size += 0.5f).Floor(); XElement *itemElement = item->GetElement(); itemElement->SetInt(TEXT("x"), int(item->pos.x)); itemElement->SetInt(TEXT("y"), int(item->pos.y)); itemElement->SetInt(TEXT("cx"), int(item->size.x)); itemElement->SetInt(TEXT("cy"), int(item->size.y)); itemElement->SetFloat(TEXT("crop.left"), item->crop.x); itemElement->SetFloat(TEXT("crop.top"), item->crop.y); itemElement->SetFloat(TEXT("crop.right"), item->crop.w); itemElement->SetFloat(TEXT("crop.bottom"), item->crop.z); } App->modifyType = ItemModifyType_None; } App->bMouseDown = false; App->bRMouseDown = false; } } if(message == WM_RBUTTONUP) { HMENU hPopup = CreatePopupMenu(); //--------------------------------------------------- if (App->bRunning) { HMENU hProjector = CreatePopupMenu(); AppendMenu(hProjector, MF_STRING | (App->bProjector ? 0 : MF_CHECKED), ID_PROJECTOR, Str("Disable")); AppendMenu(hProjector, MF_SEPARATOR, 0, 0); for (UINT i = 0; i < App->NumMonitors(); i++) { String strMonitor = Str("MonitorNum"); strMonitor.FindReplace(L"$1", UIntString(i+1)); const MonitorInfo &mi = App->GetMonitor(i); bool grayed = mi.hMonitor == MonitorFromWindow(hwndMain, MONITOR_DEFAULTTONULL); bool enabled = App->bProjector && App->projectorMonitorID == i; AppendMenu(hProjector, MF_STRING | (enabled ? MF_CHECKED : 0) | (grayed ? MF_GRAYED : 0), ID_PROJECTOR+i+1, strMonitor); } AppendMenu(hPopup, MF_STRING|MF_POPUP, (UINT_PTR)hProjector, Str("MainMenu.Settings.Projector")); AppendMenu(hPopup, MF_SEPARATOR, 0, 0); } //--------------------------------------------------- AppendMenu(hPopup, MF_STRING | (App->bFullscreenMode ? MF_CHECKED : 0), ID_TOGGLEFULLSCREEN, Str("MainMenu.Settings.FullscreenMode")); HWND hwndSources = GetDlgItem(hwndMain, ID_SOURCES); int numItems = ListView_GetItemCount(hwndSources); bool bSelected = ListView_GetSelectedCount(hwndSources) != 0; HMENU hMenuPreview; if (App->IsRunning() && bSelected) hMenuPreview = CreatePopupMenu(); else { hMenuPreview = hPopup; AppendMenu(hMenuPreview, MF_SEPARATOR, 0, 0); } AppendMenu(hMenuPreview, MF_STRING | (!App->renderFrameIn1To1Mode ? MF_CHECKED : 0), ID_PREVIEWSCALETOFITMODE, Str("RenderView.ViewModeScaleToFit")); AppendMenu(hMenuPreview, MF_STRING | (App->renderFrameIn1To1Mode ? MF_CHECKED : 0), ID_PREVIEW1TO1MODE, Str("RenderView.ViewMode1To1")); AppendMenu(hMenuPreview, MF_SEPARATOR, 0, 0); AppendMenu(hMenuPreview, MF_STRING | (App->bRenderViewEnabled ? MF_CHECKED : 0), ID_TOGGLERENDERVIEW, Str("RenderView.EnableView")); AppendMenu(hMenuPreview, MF_STRING | (App->bPanelVisible ? MF_CHECKED : 0), ID_TOGGLEPANEL, Str("RenderView.DisplayPanel")); if (App->IsRunning()) { if (bSelected) AppendMenu(hPopup, MF_STRING|MF_POPUP, (UINT_PTR)hMenuPreview, Str("Preview")); App->AppendModifyListbox(hwndSources, hPopup, ID_SOURCES, numItems, bSelected); } POINT p; GetCursorPos(&p); int ret = (int)TrackPopupMenuEx(hPopup, TPM_RETURNCMD | TPM_LEFTALIGN | TPM_RIGHTBUTTON, p.x, p.y, hwndMain, NULL); App->TrackModifyListbox(hwnd, ret); DestroyMenu(hPopup); } } else if(message == WM_GETDLGCODE && App->bEditMode) { return DLGC_WANTARROWS; } else if(message == WM_KEYDOWN && App->bRunning && App->bEditMode) { int dx, dy ; dx = dy = 0; switch(wParam) { case VK_UP: dy = -1; break; case VK_DOWN: dy = 1; break; case VK_RIGHT: dx = 1; break; case VK_LEFT: dx = -1; break; default: return DefWindowProc(hwnd, message, wParam, lParam); } App->MoveItemsByPixels(dx, dy); return 0; } else if(message == WM_KEYUP && App->bEditMode) { return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } LRESULT CALLBACK OBS::LogWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_SIZE: RECT client; GetClientRect(hwnd, &client); MoveWindow(hwndLog, client.left, client.top, client.right, client.bottom, true); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case ID_LOG_WINDOW: OBSUpdateLog(); break; } break; case WM_CLOSE: ShowWindow(hwnd, SW_HIDE); return 0; } return DefWindowProc(hwnd, message, wParam, lParam); } typedef CTSTR (*GETPLUGINNAMEPROC)(); typedef CTSTR (*GETPLUGINDESCRIPTIONPROC)(); typedef void (*CONFIGUREPLUGINPROC)(HWND); INT_PTR CALLBACK OBS::PluginsDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_INITDIALOG: { LocalizeWindow(hwnd); HWND hwndPlugins = GetDlgItem(hwnd, IDC_PLUGINS); for(UINT i=0; iplugins.Num(); i++) { PluginInfo &pluginInfo = App->plugins[i]; GETPLUGINNAMEPROC getName = (GETPLUGINNAMEPROC)GetProcAddress(pluginInfo.hModule, "GetPluginName"); CTSTR lpName; if(getName) lpName = getName(); else lpName = pluginInfo.strFile; UINT id = (UINT)SendMessage(hwndPlugins, LB_ADDSTRING, 0, (LPARAM)lpName); SendMessage(hwndPlugins, LB_SETITEMDATA, id, (LPARAM)i); } return TRUE; } case WM_COMMAND: switch(LOWORD(wParam)) { case IDC_PLUGINS: if(HIWORD(wParam) == LBN_SELCHANGE) { UINT id = (UINT)SendMessage((HWND)lParam, LB_GETCURSEL, 0, 0); if(id == LB_ERR) break; UINT pluginID = (UINT)SendMessage((HWND)lParam, LB_GETITEMDATA, id, 0); PluginInfo &pluginInfo = App->plugins[pluginID]; //------------------------------------- GETPLUGINDESCRIPTIONPROC getDescription = (GETPLUGINDESCRIPTIONPROC)GetProcAddress(pluginInfo.hModule, "GetPluginDescription"); CTSTR lpDescription = NULL; if(getDescription) lpDescription = getDescription(); String strText; strText << Str("Plugins.Filename") << TEXT(" ") << pluginInfo.strFile; if(lpDescription) strText << TEXT("\r\n\r\n") << Str("Plugins.Description") << TEXT("\r\n") << lpDescription; SetWindowText(GetDlgItem(hwnd, IDC_DESCRIPTION), strText.Array()); //------------------------------------- CONFIGUREPLUGINPROC configPlugin = (CONFIGUREPLUGINPROC)GetProcAddress(pluginInfo.hModule, "ConfigPlugin"); EnableWindow(GetDlgItem(hwnd, IDC_CONFIG), configPlugin != NULL ? TRUE : FALSE); } break; case IDC_CONFIG: if(HIWORD(wParam) == BN_CLICKED) { HWND hwndPlugins = GetDlgItem(hwnd, IDC_PLUGINS); UINT id = (UINT)SendMessage(hwndPlugins, LB_GETCURSEL, 0, 0); if(id == LB_ERR) break; UINT pluginID = (UINT)SendMessage(hwndPlugins, LB_GETITEMDATA, id, 0); PluginInfo &pluginInfo = App->plugins[pluginID]; //------------------------------------- CONFIGUREPLUGINPROC configPlugin = (CONFIGUREPLUGINPROC)GetProcAddress(pluginInfo.hModule, "ConfigPlugin"); configPlugin(hwnd); } break; case IDOK: EndDialog(hwnd, IDOK); } break; case WM_CLOSE: EndDialog(hwnd, IDOK); } return FALSE; } int SetSliderText(HWND hwndParent, int controlSlider, int controlText) { HWND hwndSlider = GetDlgItem(hwndParent, controlSlider); HWND hwndText = GetDlgItem(hwndParent, controlText); int sliderVal = (int)SendMessage(hwndSlider, TBM_GETPOS, 0, 0); float floatVal = float(sliderVal)*0.01f; SetWindowText(hwndText, FormattedString(TEXT("%.02f"), floatVal)); return sliderVal; }