/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2006-2008 Sven Eberhardt * Copyright (c) 2008 Matthes Bender * Copyright (c) 2009 Nicolas Hake * Copyright (c) 2010 Günther Brammer * Copyright (c) 2008-2009, RedWolf Design GmbH, http://www.clonk.de * * Portions might be copyrighted by other authors who have contributed * to OpenClonk. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * See isc_license.txt for full license and disclaimer. * * "Clonk" is a registered trademark of Matthes Bender. * See clonk_trademark_license.txt for full license. */ // file selection dialogs #include #include #include // only for single use of ::GraphicsResource.fctOKCancel below... #include #include #ifdef _WIN32 #ifndef _WIN32_IE #define _WIN32_IE 0x0400 #endif #include #ifndef CSIDL_MYPICTURES #define CSIDL_MYPICTURES 0x0027 #endif #endif // def 1 if gfx loading works in background thread. Right now, it doesn't // C4Group and C4Surface just don't like it // So for now, the loading will be done in 1/10 of the frames in OnIdle of the dialog #define USE_BACKGROUND_THREAD_LOAD 0 // --------------------------------------------------- // C4FileSelDlg::ListItem C4FileSelDlg::ListItem::ListItem(const char *szFilename) : C4GUI::Control(C4Rect(0,0,0,0)) { if (szFilename) sFilename.Copy(szFilename); else sFilename.Clear(); } C4FileSelDlg::ListItem::~ListItem() { } // --------------------------------------------------- // C4FileSelDlg::DefaultListItem C4FileSelDlg::DefaultListItem::DefaultListItem(const char *szFilename, bool fTruncateExtension, bool fCheckbox, bool fGrayed, C4GUI::Icons eIcon) : C4FileSelDlg::ListItem(szFilename), pLbl(NULL), pCheck(NULL), pKeyCheck(NULL), fGrayed(fGrayed) { StdStrBuf sLabel; if (szFilename) sLabel.Ref(::GetFilename(szFilename)); else sLabel.Ref(LoadResStr("IDS_CTL_NONE")); if (szFilename && fTruncateExtension) { sLabel.Copy(); char *szFilename = sLabel.GrabPointer(); RemoveExtension(szFilename); sLabel.Take(szFilename); } rcBounds.Hgt = ::GraphicsResource.TextFont.GetLineHeight(); UpdateSize(); C4GUI::ComponentAligner caMain(GetContainedClientRect(),0,0); int32_t iHeight = caMain.GetInnerHeight(); if (fCheckbox) { pCheck = new C4GUI::CheckBox(caMain.GetFromLeft(iHeight), NULL, false); if (fGrayed) pCheck->SetEnabled(false); AddElement(pCheck); pKeyCheck = new C4KeyBinding(C4KeyCodeEx(K_SPACE), "FileSelToggleFileActive", KEYSCOPE_Gui, new C4GUI::ControlKeyCB(*this, &ListItem::UserToggleCheck), C4CustomKey::PRIO_Ctrl); } C4GUI::Icon *pIco = new C4GUI::Icon(caMain.GetFromLeft(iHeight), eIcon); AddElement(pIco); pLbl = new C4GUI::Label(sLabel.getData(), caMain.GetAll(), ALeft, fGrayed ? C4GUI_CheckboxDisabledFontClr : C4GUI_CheckboxFontClr); AddElement(pLbl); } C4FileSelDlg::DefaultListItem::~DefaultListItem() { if (pKeyCheck) delete pKeyCheck; } void C4FileSelDlg::DefaultListItem::UpdateOwnPos() { BaseClass::UpdateOwnPos(); if (!pLbl) return; C4GUI::ComponentAligner caMain(GetContainedClientRect(),0,0); caMain.GetFromLeft(caMain.GetInnerHeight()*(1+!!pCheck)); pLbl->SetBounds(caMain.GetAll()); } bool C4FileSelDlg::DefaultListItem::IsChecked() const { return pCheck ? pCheck->GetChecked() : false; } void C4FileSelDlg::DefaultListItem::SetChecked(bool fChecked) { // store new state in checkbox if (pCheck) pCheck->SetChecked(fChecked); } bool C4FileSelDlg::DefaultListItem::UserToggleCheck() { // toggle if possible if (pCheck && !IsGrayed()) { pCheck->ToggleCheck(true); return true; } return false; } // --------------------------------------------------- // C4FileSelDlg C4FileSelDlg::C4FileSelDlg(const char *szRootPath, const char *szTitle, C4FileSel_BaseCB *pSelCallback, bool fInitElements) : C4GUI::Dialog(BoundBy(C4GUI::GetScreenWdt()*2/3+10, 300,600), BoundBy(C4GUI::GetScreenHgt()*2/3+10, 220,500), szTitle, false), pLocationComboBox(NULL), pFileListBox(NULL), pSelectionInfoBox(NULL), btnOK(NULL), pLocations(NULL), iLocationCount(0), pSelection(NULL), pSelCallback(pSelCallback) { sTitle.Copy(szTitle); // key bindings pKeyRefresh = new C4KeyBinding(C4KeyCodeEx(K_F5), "FileSelReload", KEYSCOPE_Gui, new C4GUI::DlgKeyCB(*this, &C4FileSelDlg::KeyRefresh), C4CustomKey::PRIO_CtrlOverride); pKeyEnterOverride = new C4KeyBinding(C4KeyCodeEx(K_RETURN), "FileSelConfirm", KEYSCOPE_Gui, new C4GUI::DlgKeyCB(*this, &C4FileSelDlg::KeyEnter), C4CustomKey::PRIO_CtrlOverride); if (fInitElements) InitElements(); sPath.Copy(szRootPath); } void C4FileSelDlg::InitElements() { UpdateSize(); CStdFont *pUseFont = &(::GraphicsResource.TextFont); // main calcs bool fHasOptions = HasExtraOptions(); C4GUI::ComponentAligner caMain(GetClientRect(), 0,0, true); C4Rect rcOptions; C4GUI::ComponentAligner caButtonArea(caMain.GetFromBottom(C4GUI_ButtonAreaHgt, 2*C4GUI_DefButton2Wdt+4*C4GUI_DefButton2HSpace),C4GUI_DefButton2HSpace,(C4GUI_ButtonAreaHgt-C4GUI_ButtonHgt)/2); if (fHasOptions) rcOptions = caMain.GetFromBottom(pUseFont->GetLineHeight() + 2*C4GUI_DefDlgSmallIndent); C4GUI::ComponentAligner caUpperArea(caMain.GetAll(), C4GUI_DefDlgIndent, C4GUI_DefDlgIndent, true); // create file selection area if (iLocationCount) { C4GUI::ComponentAligner caLocations(caUpperArea.GetFromTop(C4GUI::ComboBox::GetDefaultHeight() + 2*C4GUI_DefDlgSmallIndent), C4GUI_DefDlgIndent,C4GUI_DefDlgSmallIndent, false); StdStrBuf sText(LoadResStr("IDS_TEXT_LOCATION")); AddElement(new C4GUI::Label(sText.getData(), caLocations.GetFromLeft(pUseFont->GetTextWidth(sText.getData())), ALeft)); pLocationComboBox = new C4GUI::ComboBox(caLocations.GetAll()); pLocationComboBox->SetComboCB(new C4GUI::ComboBox_FillCallback(this, &C4FileSelDlg::OnLocationComboFill, &C4FileSelDlg::OnLocationComboSelChange)); pLocationComboBox->SetText(pLocations[0].sName.getData()); } // create file selection area bool fHasPreview = HasPreviewArea(); pFileListBox = new C4GUI::ListBox(fHasPreview ? caUpperArea.GetFromLeft(caUpperArea.GetWidth()/2) : caUpperArea.GetAll(), GetFileSelColWidth()); pFileListBox ->SetSelectionChangeCallbackFn(new C4GUI::CallbackHandler(this, &C4FileSelDlg::OnSelChange)); pFileListBox ->SetSelectionDblClickFn(new C4GUI::CallbackHandler(this, &C4FileSelDlg::OnSelDblClick)); if (fHasPreview) { caUpperArea.ExpandLeft(C4GUI_DefDlgIndent); pSelectionInfoBox = new C4GUI::TextWindow(caUpperArea.GetAll()); pSelectionInfoBox->SetDecoration(true, true, NULL, true); } // create button area C4GUI::Button *btnAbort = new C4GUI::CancelButton(caButtonArea.GetFromRight(C4GUI_DefButton2Wdt)); btnOK = new C4GUI::OKButton(caButtonArea.GetFromRight(C4GUI_DefButton2Wdt)); // add components in tab order if (pLocationComboBox) AddElement(pLocationComboBox); AddElement(pFileListBox); if (pSelectionInfoBox) AddElement(pSelectionInfoBox); if (fHasOptions) AddExtraOptions(rcOptions); AddElement(btnOK); AddElement(btnAbort); SetFocus(pFileListBox, false); // no selection yet UpdateSelection(); } C4FileSelDlg::~C4FileSelDlg() { delete [] pLocations; if (pSelCallback) delete pSelCallback; delete pKeyEnterOverride; delete pKeyRefresh; } void C4FileSelDlg::OnLocationComboFill(C4GUI::ComboBox_FillCB *pFiller) { // Add all locations for (int32_t i=0; iAddEntry(pLocations[i].sName.getData(), i); } bool C4FileSelDlg::OnLocationComboSelChange(C4GUI::ComboBox *pForCombo, int32_t idNewSelection) { SetCurrentLocation(idNewSelection, true); // No text change by caller; text alread changed by SetCurrentLocation return true; } void C4FileSelDlg::SetPath(const char *szNewPath, bool fRefresh) { sPath.Copy(szNewPath); if (fRefresh && IsShown()) UpdateFileList(); } void C4FileSelDlg::OnShown() { BaseClass::OnShown(); // load files UpdateFileList(); } void C4FileSelDlg::UserClose(bool fOK) { if (!fOK || pSelection) { // allow OK only if something is sth is selected Close(fOK); } else { GetScreen()->ShowErrorMessage(LoadResStr("IDS_ERR_PLEASESELECTAFILEFIRST")); } } void C4FileSelDlg::OnClosed(bool fOK) { if (fOK && pSelection && pSelCallback) pSelCallback->OnFileSelected(pSelection->GetFilename()); // base call: Might delete dlg BaseClass::OnClosed(fOK); } void C4FileSelDlg::OnSelDblClick(class C4GUI::Element *pEl) { // item double-click: confirms with this file in single mode; toggles selection in multi mode if (IsMultiSelection()) { ListItem *pItem = static_cast(pEl); pItem->UserToggleCheck(); } else UserClose(true); } C4FileSelDlg::ListItem *C4FileSelDlg::CreateListItem(const char *szFilename) { // Default list item if (szFilename) return new DefaultListItem(szFilename, !!GetFileMask(), IsMultiSelection(), IsItemGrayed(szFilename), GetFileItemIcon()); else return new DefaultListItem(NULL, false, IsMultiSelection(), false, GetFileItemIcon()); } void C4FileSelDlg::UpdateFileList() { BeginFileListUpdate(); // reload files C4GUI::Element *pEl; while ((pEl = pFileListBox->GetFirst())) delete pEl; // file items StdStrBuf sSearch; const char *szFileMask = GetFileMask(); for (DirectoryIterator iter(sPath.getData()); *iter; ++iter) if (!szFileMask || WildcardListMatch(szFileMask, *iter)) pFileListBox->AddElement(CreateListItem(*iter)); // none-item? if (HasNoneItem()) { pFileListBox->AddElement(CreateListItem(NULL)); } // list now done EndFileListUpdate(); // path into title const char *szPath = sPath.getData(); SetTitle(*szPath ? FormatString("%s [%s]", sTitle.getData(), szPath).getData() : sTitle.getData()); // initial no-selection UpdateSelection(); } void C4FileSelDlg::UpdateSelection() { // update selection from list pSelection = static_cast(pFileListBox->GetSelectedItem()); // OK button only available if selection // SetEnabled would look a lot better here, but it doesn't exist yet :( //btnOK->SetEnabled(!!pSelection || IsMultiSelection()); // selection preview, if enabled if (pSelectionInfoBox) { // default empty pSelectionInfoBox->ClearText(false); if (!pSelection) { pSelectionInfoBox->UpdateHeight(); return; } // add selection description if (pSelection->GetFilename()) pSelectionInfoBox->AddTextLine(pSelection->GetFilename(), &::GraphicsResource.TextFont, C4GUI_MessageFontClr, true, false); } } void C4FileSelDlg::SetSelection(const char *szNewSelection, bool fFilenameOnly) { // check all selected definitions for (ListItem *pFileItem = static_cast(pFileListBox->GetFirst()); pFileItem; pFileItem = static_cast(pFileItem->GetNext())) { const char *szFileItemFilename = pFileItem->GetFilename(); if (fFilenameOnly) szFileItemFilename = GetFilename(szFileItemFilename); pFileItem->SetChecked(SIsModule(szNewSelection, szFileItemFilename)); } } StdStrBuf C4FileSelDlg::GetSelection(const char *szFixedSelection, bool fFilenameOnly) const { StdStrBuf sResult; if (!IsMultiSelection()) { // get single selected file for single selection dlg if (pSelection) sResult.Copy(fFilenameOnly ? GetFilename(pSelection->GetFilename()) : pSelection->GetFilename()); } else { // force fixed selection first if (szFixedSelection) sResult.Append(szFixedSelection); // get ';'-separated list for multi selection dlg for (ListItem *pFileItem = static_cast(pFileListBox->GetFirst()); pFileItem; pFileItem = static_cast(pFileItem->GetNext())) if (pFileItem->IsChecked()) { const char *szAppendFilename = pFileItem->GetFilename(); if (fFilenameOnly) szAppendFilename = GetFilename(szAppendFilename); // prevent adding entries twice (especially those from the fixed selection list) if (!SIsModule(sResult.getData(), szAppendFilename)) { if (sResult.getLength()) sResult.AppendChar(';'); sResult.Append(szAppendFilename); } } } return sResult; } void C4FileSelDlg::AddLocation(const char *szName, const char *szPath) { // add to list int32_t iNewLocCount = iLocationCount+1; Location *pNewLocations = new Location[iNewLocCount]; for (int32_t i=0; i(idx, 0,iLocationCount)) return; // update ComboBox-text iCurrentLocationIndex = idx; if (pLocationComboBox) pLocationComboBox->SetText(pLocations[idx].sName.getData()); // set new path SetPath(pLocations[idx].sPath.getData(), fRefresh); } // --------------------------------------------------- // C4PlayerSelDlg C4PlayerSelDlg::C4PlayerSelDlg(C4FileSel_BaseCB *pSelCallback) : C4FileSelDlg(Config.General.UserDataPath, LoadResStr("IDS_MSG_SELECTPLR"), pSelCallback) { } // --------------------------------------------------- // C4DefinitionSelDlg C4DefinitionSelDlg::C4DefinitionSelDlg(C4FileSel_BaseCB *pSelCallback, const char *szFixedSelection) : C4FileSelDlg(Config.General.UserDataPath, FormatString(LoadResStr("IDS_MSG_SELECT"), LoadResStr("IDS_DLG_DEFINITIONS")).getData(), pSelCallback) { if (szFixedSelection) sFixedSelection.Copy(szFixedSelection); } void C4DefinitionSelDlg::OnShown() { // base call: load file list C4FileSelDlg::OnShown(); // initial selection if (sFixedSelection) SetSelection(sFixedSelection.getData(), true); } bool C4DefinitionSelDlg::IsItemGrayed(const char *szFilename) const { // cannot change initial selection if (!sFixedSelection) return false; return SIsModule(sFixedSelection.getData(), GetFilename(szFilename)); } bool C4DefinitionSelDlg::SelectDefinitions(C4GUI::Screen *pOnScreen, StdStrBuf *pSelection) { // let the user select definitions by showing a modal selection dialog C4DefinitionSelDlg *pDlg = new C4DefinitionSelDlg(NULL, pSelection->getData()); bool fResult; if ((fResult = pOnScreen->ShowModalDlg(pDlg, false))) { pSelection->Copy(pDlg->GetSelection(pSelection->getData(), true)); } delete pDlg; return fResult; } // --------------------------------------------------- // C4PortraitSelDlg::ListItem C4PortraitSelDlg::ListItem::ListItem(const char *szFilename) : C4FileSelDlg::ListItem(szFilename) , fError(false), fLoaded(false) { CStdFont *pUseFont = &(::GraphicsResource.MiniFont); // determine label text StdStrBuf sDisplayLabel; if (szFilename) { sDisplayLabel.Copy(::GetFilename(szFilename)); ::RemoveExtension(&sDisplayLabel); } else { sDisplayLabel.Ref(LoadResStr("IDS_MSG_NOPORTRAIT")); } // insert linebreaks into label text int32_t iLineHgt = Max(pUseFont->BreakMessage(sDisplayLabel.getData(), ImagePreviewSize-6, &sFilenameLabelText, false), 1); // set size SetBounds(C4Rect(0,0,ImagePreviewSize,ImagePreviewSize+iLineHgt)); } void C4PortraitSelDlg::ListItem::Load() { if (sFilename) { // safety fLoaded = false; // load image file C4Group SrcGrp; StdStrBuf sParentPath; GetParentPath(sFilename.getData(), &sParentPath); bool fLoadError = true; if (SrcGrp.Open(sParentPath.getData())) if (fctLoadedImage.Load(SrcGrp, ::GetFilename(sFilename.getData()))) { // image loaded. Can only be put into facet by main thread, because those operations aren't thread safe fLoaded = true; fLoadError = false; } SrcGrp.Close(); fError = fLoadError; } } void C4PortraitSelDlg::ListItem::DrawElement(C4TargetFacet &cgo) { // Scale down newly loaded image? if (fLoaded) { fLoaded = false; if (!fctImage.CopyFromSfcMaxSize(fctLoadedImage.GetFace(), ImagePreviewSize)) fError = true; fctLoadedImage.GetFace().Clear(); fctLoadedImage.Clear(); } // Draw picture CStdFont *pUseFont = &(::GraphicsResource.MiniFont); C4Facet cgoPicture(cgo.Surface, cgo.TargetX+rcBounds.x, cgo.TargetY+rcBounds.y, ImagePreviewSize, ImagePreviewSize); if (fError || !sFilename) { C4Facet &fctNoneImg = ::GraphicsResource.fctOKCancel; fctNoneImg.Draw(cgoPicture.Surface, cgoPicture.X+(cgoPicture.Wdt-fctNoneImg.Wdt)/2, cgoPicture.Y+(cgoPicture.Hgt-fctNoneImg.Hgt)/2, 1,0); } else { if (!fctImage.Surface) { // not loaded yet pDraw->TextOut(LoadResStr("IDS_PRC_INITIALIZE"), ::GraphicsResource.MiniFont, 1.0f, cgo.Surface, cgoPicture.X+cgoPicture.Wdt/2, cgoPicture.Y+(cgoPicture.Hgt-::GraphicsResource.MiniFont.GetLineHeight())/2, C4GUI_StatusFontClr, ACenter, false); } else { fctImage.Draw(cgoPicture); } } // draw filename pDraw->TextOut(sFilenameLabelText.getData(), *pUseFont, 1.0f, cgo.Surface, cgoPicture.X+rcBounds.Wdt/2, cgoPicture.Y+cgoPicture.Hgt, C4GUI_MessageFontClr, ACenter, false); } // --------------------------------------------------- // C4PortraitSelDlg::LoaderThread void C4PortraitSelDlg::LoaderThread::ClearLoadItems() { // stop thread so list can be accessed Stop(); // clear list LoadItems.clear(); } void C4PortraitSelDlg::LoaderThread::AddLoadItem(ListItem *pItem) { // not to be called when thread is running! assert(!IsStarted()); LoadItems.push_back(pItem); } void C4PortraitSelDlg::LoaderThread::Execute() { // list empty? if (!LoadItems.size()) { // then we're done! SignalStop(); return; } // load one item at the time ListItem *pLoadItem = LoadItems.front(); pLoadItem->Load(); LoadItems.erase(LoadItems.begin()); } // --------------------------------------------------- // C4PortraitSelDlg C4PortraitSelDlg::C4PortraitSelDlg(C4FileSel_BaseCB *pSelCallback) : C4FileSelDlg(Config.General.SystemDataPath, FormatString(LoadResStr("IDS_MSG_SELECT"), LoadResStr("IDS_TYPE_PORTRAIT")).getData(), pSelCallback, false) { char path[_MAX_PATH+1]; // add common picture locations StdStrBuf strLocation; SCopy(Config.General.UserDataPath, path, _MAX_PATH); TruncateBackslash(path); strLocation.Format("%s %s", C4ENGINECAPTION, LoadResStr("IDS_TEXT_USERPATH")); AddLocation(strLocation.getData(), path); SCopy(Config.General.SystemDataPath, path, _MAX_PATH); TruncateBackslash(path); strLocation.Format("%s %s", C4ENGINECAPTION, LoadResStr("IDS_TEXT_PROGRAMDIRECTORY")); AddCheckedLocation(strLocation.getData(), path); #ifdef _WIN32 wchar_t wpath[MAX_PATH+1]; if (SHGetSpecialFolderPathW(NULL, wpath, CSIDL_PERSONAL, false)) AddCheckedLocation(LoadResStr("IDS_TEXT_MYDOCUMENTS"), StdStrBuf(wpath).getData()); if (SHGetSpecialFolderPathW(NULL, wpath, CSIDL_MYPICTURES, false)) AddCheckedLocation(LoadResStr("IDS_TEXT_MYPICTURES"), StdStrBuf(wpath).getData()); if (SHGetSpecialFolderPathW(NULL, wpath, CSIDL_DESKTOPDIRECTORY, false)) AddCheckedLocation(LoadResStr("IDS_TEXT_DESKTOP"), StdStrBuf(wpath).getData()); #endif #ifdef __APPLE__ AddCheckedLocation(LoadResStr("IDS_TEXT_HOME"), getenv("HOME")); #else AddCheckedLocation(LoadResStr("IDS_TEXT_HOMEFOLDER"), getenv("HOME")); #endif #ifndef _WIN32 sprintf(path, "%s%c%s", getenv("HOME"), (char)DirectorySeparator, (const char *)"Desktop"); AddCheckedLocation(LoadResStr("IDS_TEXT_DESKTOP"), path); #endif // build dialog InitElements(); // select last location SetCurrentLocation(Config.Startup.LastPortraitFolderIdx, false); } void C4PortraitSelDlg::OnClosed(bool fOK) { // remember location Config.Startup.LastPortraitFolderIdx = GetCurrentLocationIndex(); // inherited C4FileSelDlg::OnClosed(fOK); } C4FileSelDlg::ListItem *C4PortraitSelDlg::CreateListItem(const char *szFilename) { // use own list item type ListItem *pNew = new ListItem(szFilename);; // schedule image loading ImageLoader.AddLoadItem(pNew); return pNew; } void C4PortraitSelDlg::BeginFileListUpdate() { // new file list. Stop loading current ImageLoader.ClearLoadItems(); } void C4PortraitSelDlg::EndFileListUpdate() { #if USE_BACKGROUND_THREAD_LOAD // Begin loading images ImageLoader.Start(); #endif } void C4PortraitSelDlg::OnIdle() { #if !USE_BACKGROUND_THREAD_LOAD // no multithreading? Workaround for image loading then... static int32_t i = 0; if (!(i++%10)) ImageLoader.Execute(); #endif } bool C4PortraitSelDlg::SelectPortrait(C4GUI::Screen *pOnScreen, StdStrBuf *pSelection) { // let the user select a portrait by showing a modal selection dialog C4PortraitSelDlg *pDlg = new C4PortraitSelDlg(NULL); bool fResult; if ((fResult = pOnScreen->ShowModalDlg(pDlg, false))) { pSelection->Take(pDlg->GetSelection(NULL, false)); } delete pDlg; return fResult; }