diff --git a/Import/GacUI.cpp b/Import/GacUI.cpp index aa1bf970..c857e22b 100644 --- a/Import/GacUI.cpp +++ b/Import/GacUI.cpp @@ -8,398 +8,32306 @@ DEVELOPER: Zihan Chen(vczh) #endif /*********************************************************************** -.\RESOURCES\GUIRESOURCETYPERESOLVERS.CPP +.\GACUIREFLECTIONHELPER.CPP ***********************************************************************/ +namespace vl +{ + namespace reflection + { + namespace description + { + using namespace parsing; + using namespace parsing::tabling; + using namespace parsing::xml; + using namespace stream; + using namespace collections; + using namespace presentation; + using namespace presentation::elements; + using namespace presentation::compositions; + using namespace presentation::controls; + using namespace presentation::theme; + using namespace presentation::templates; + +/*********************************************************************** +Serialization (Color) +***********************************************************************/ + + Color TypedValueSerializerProvider::GetDefaultValue() + { + return Color(); + } + + bool TypedValueSerializerProvider::Serialize(const Color& input, WString& output) + { + output = input.ToString(); + return true; + } + + bool TypedValueSerializerProvider::Deserialize(const WString& input, Color& output) + { + output = Color::Parse(input); + return true; + } + + IBoxedValue::CompareResult TypedValueSerializerProvider::Compare(const presentation::Color& a, const presentation::Color& b) + { + return TypedValueSerializerProvider::Compare(a.value, b.value); + } + +/*********************************************************************** +Serialization (DocumentFontSize) +***********************************************************************/ + + DocumentFontSize TypedValueSerializerProvider::GetDefaultValue() + { + return DocumentFontSize(); + } + + bool TypedValueSerializerProvider::Serialize(const DocumentFontSize& input, WString& output) + { + output = input.ToString(); + return true; + } + + bool TypedValueSerializerProvider::Deserialize(const WString& input, DocumentFontSize& output) + { + output = DocumentFontSize::Parse(input); + return true; + } + + IBoxedValue::CompareResult TypedValueSerializerProvider::Compare(const presentation::DocumentFontSize& a, const presentation::DocumentFontSize& b) + { + return TypedValueSerializerProvider::Compare(a.ToString(), b.ToString()); + } + +/*********************************************************************** +Serialization (GlobalStringKey) +***********************************************************************/ + + GlobalStringKey TypedValueSerializerProvider::GetDefaultValue() + { + return GlobalStringKey(); + } + + bool TypedValueSerializerProvider::Serialize(const GlobalStringKey& input, WString& output) + { + output = input.ToString(); + return true; + } + + bool TypedValueSerializerProvider::Deserialize(const WString& input, GlobalStringKey& output) + { + output = GlobalStringKey::Get(input); + return true; + } + + IBoxedValue::CompareResult TypedValueSerializerProvider::Compare(const presentation::GlobalStringKey& a, const presentation::GlobalStringKey& b) + { + return TypedValueSerializerProvider::Compare(a.ToString(), b.ToString()); + } + +/*********************************************************************** +External Functions (Basic) +***********************************************************************/ + + Ptr INativeImage_Constructor(const WString& path) + { + return GetCurrentController()->ImageService()->CreateImageFromFile(path); + } + + INativeCursor* INativeCursor_Constructor1() + { + return GetCurrentController()->ResourceService()->GetDefaultSystemCursor(); + } + + INativeCursor* INativeCursor_Constructor2(INativeCursor::SystemCursorType type) + { + return GetCurrentController()->ResourceService()->GetSystemCursor(type); + } + +/*********************************************************************** +External Functions (Elements) +***********************************************************************/ + + text::TextLines* GuiColorizedTextElement_GetLines(GuiColorizedTextElement* thisObject) + { + return &thisObject->GetLines(); + } + +/*********************************************************************** +External Functions (Compositions) +***********************************************************************/ + + void GuiTableComposition_SetRows(GuiTableComposition* thisObject, vint value) + { + vint columns = thisObject->GetColumns(); + if (columns <= 0) columns = 1; + thisObject->SetRowsAndColumns(value, columns); + } + + void GuiTableComposition_SetColumns(GuiTableComposition* thisObject, vint value) + { + vint row = thisObject->GetRows(); + if (row <= 0) row = 1; + thisObject->SetRowsAndColumns(row, value); + } + + void IGuiAltActionHost_CollectAltActions(IGuiAltActionHost* host, List& actions) + { + Group group; + host->CollectAltActions(group); + for (vint i = 0; i < group.Count(); i++) + { + CopyFrom(actions, group.GetByIndex(i), true); + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\GUIAPPLICATION.CPP +***********************************************************************/ + +extern void GuiMain(); + namespace vl { namespace presentation { - using namespace collections; - using namespace controls; - using namespace parsing; - using namespace parsing::tabling; - using namespace parsing::xml; - using namespace stream; + namespace controls + { + using namespace collections; + using namespace compositions; + using namespace theme; + using namespace description; /*********************************************************************** -Image Type Resolver (Image) +GuiApplication ***********************************************************************/ - class GuiResourceImageTypeResolver - : public Object - , public IGuiResourceTypeResolver - , private IGuiResourceTypeResolver_DirectLoadXml - , private IGuiResourceTypeResolver_DirectLoadStream - { - public: - WString GetType()override + void GuiApplication::InvokeClipboardNotify(compositions::GuiGraphicsComposition* composition, compositions::GuiEventArgs& arguments) { - return L"Image"; + if(composition->HasEventReceiver()) + { + composition->GetEventReceiver()->clipboardNotify.Execute(arguments); + } + FOREACH(GuiGraphicsComposition*, subComposition, composition->Children()) + { + InvokeClipboardNotify(subComposition, arguments); + } } - bool XmlSerializable()override + void GuiApplication::LeftButtonDown(Point position) { - return true; + OnMouseDown(position); } - bool StreamSerializable()override + void GuiApplication::LeftButtonUp(Point position) { - return true; } - IGuiResourceTypeResolver_DirectLoadXml* DirectLoadXml()override + void GuiApplication::RightButtonDown(Point position) { - return this; + OnMouseDown(position); } - IGuiResourceTypeResolver_DirectLoadStream* DirectLoadStream()override + void GuiApplication::RightButtonUp(Point position) { - return this; } - Ptr Serialize(Ptr resource, Ptr content)override + void GuiApplication::ClipboardUpdated() + { + for(vint i=0;iGetNotifyEventArguments(); + windows[i]->ClipboardUpdated.Execute(arguments); + InvokeClipboardNotify(windows[i]->GetBoundsComposition(), arguments); + } + } + + GuiApplication::GuiApplication() + :locale(Locale::UserDefault()) + { + GetCurrentController()->CallbackService()->InstallListener(this); + } + + GuiApplication::~GuiApplication() + { + if(sharedTooltipControl) + { + delete sharedTooltipControl; + sharedTooltipControl=0; + } + GetCurrentController()->CallbackService()->UninstallListener(this); + } + + INativeWindow* GuiApplication::GetThreadContextNativeWindow(GuiControlHost* controlHost) { return nullptr; } - void SerializePrecompiled(Ptr resource, Ptr content, stream::IStream& stream)override + void GuiApplication::RegisterWindow(GuiWindow* window) { - auto obj = content.Cast(); - stream::internal::ContextFreeWriter writer(stream); - FileStream fileStream(resource->GetFileAbsolutePath(), FileStream::ReadOnly); - writer << (stream::IStream&)fileStream; + windows.Add(window); } - Ptr ResolveResource(Ptr resource, Ptr element, GuiResourceError::List& errors)override + void GuiApplication::UnregisterWindow(GuiWindow* window) { - errors.Add(GuiResourceError({ resource }, L"Image should load from file.")); - return nullptr; + windows.Remove(window); } - Ptr ResolveResource(Ptr resource, const WString& path, GuiResourceError::List& errors)override + void GuiApplication::RegisterPopupOpened(GuiPopup* popup) { - Ptr image = GetCurrentController()->ImageService()->CreateImageFromFile(path); - if(image) + vint index=openingPopups.IndexOf(popup); + if(index==-1) { - return new GuiImageData(image, 0); - } - else - { - errors.Add(GuiResourceError({ resource }, L"Failed to load file \"" + path + L"\".")); - return nullptr; + openingPopups.Add(popup); + if(openingPopups.Count()==1) + { + GetCurrentController()->InputService()->StartHookMouse(); + } } } - Ptr ResolveResourcePrecompiled(Ptr resource, stream::IStream& stream, GuiResourceError::List& errors)override + void GuiApplication::RegisterPopupClosed(GuiPopup* popup) { - stream::internal::ContextFreeReader reader(stream); - MemoryStream memoryStream; - reader << (stream::IStream&)memoryStream; - - auto image = GetCurrentController()->ImageService()->CreateImageFromStream(memoryStream); - if (image) + if(openingPopups.Remove(popup)) { - return new GuiImageData(image, 0); - } - else - { - errors.Add(GuiResourceError({ resource }, L"[BINARY] Failed to load an image from binary data in a stream.")); - return nullptr; + if(openingPopups.Count()==0) + { + GetCurrentController()->InputService()->StopHookMouse(); + } } } - }; -/*********************************************************************** -Text Type Resolver (Text) -***********************************************************************/ - - class GuiResourceTextTypeResolver - : public Object - , public IGuiResourceTypeResolver - , private IGuiResourceTypeResolver_DirectLoadXml - , private IGuiResourceTypeResolver_DirectLoadStream - { - public: - WString GetType()override + void GuiApplication::OnMouseDown(Point location) { - return L"Text"; - } - - bool XmlSerializable()override - { - return true; - } - - bool StreamSerializable()override - { - return true; - } - - IGuiResourceTypeResolver_DirectLoadXml* DirectLoadXml()override - { - return this; - } - - IGuiResourceTypeResolver_DirectLoadStream* DirectLoadStream()override - { - return this; - } - - Ptr Serialize(Ptr resource, Ptr content)override - { - if (auto obj = content.Cast()) + GuiWindow* window=GetWindow(location); + for(vint i=0;i(); - xmlContent->content.value = obj->GetText(); + if(windows[i]!=window) + { + windows[i]->MouseClickedOnOtherWindow(window); + } + } + } - auto xmlText = MakePtr(); - xmlText->name.value = L"Text"; - xmlText->subNodes.Add(xmlContent); + void GuiApplication::TooltipMouseEnter(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + sharedTooltipHovering=true; + } - return xmlText; + void GuiApplication::TooltipMouseLeave(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + sharedTooltipHovering=false; + if(sharedTooltipClosing) + { + CloseTooltip(); + } + } + + Locale GuiApplication::GetLocale() + { + return locale; + } + + void GuiApplication::SetLocale(Locale value) + { + if (locale != value) + { + locale = value; + LocaleChanged(); + } + } + + void GuiApplication::Run(GuiWindow* _mainWindow) + { + if (!mainWindow) + { + mainWindow = _mainWindow; + GetCurrentController()->WindowService()->Run(mainWindow->GetNativeWindow()); + mainWindow = nullptr; + } + } + + GuiWindow* GuiApplication::GetMainWindow() + { + return mainWindow; + } + + const collections::List& GuiApplication::GetWindows() + { + return windows; + } + + GuiWindow* GuiApplication::GetWindow(Point location) + { + INativeWindow* nativeWindow = GetCurrentController()->WindowService()->GetWindow(location); + if (nativeWindow) + { + for (vint i = 0; i < windows.Count(); i++) + { + GuiWindow* window = windows[i]; + if (window->GetNativeWindow() == nativeWindow) + { + return window; + } + } } return 0; } - void SerializePrecompiled(Ptr resource, Ptr content, stream::IStream& stream)override + void GuiApplication::ShowTooltip(GuiControl* owner, GuiControl* tooltip, vint preferredContentWidth, Point location) { - auto obj = content.Cast(); - stream::internal::ContextFreeWriter writer(stream); - WString text = obj->GetText(); - writer << text; - } - - Ptr ResolveResource(Ptr resource, Ptr element, GuiResourceError::List& errors)override - { - return new GuiTextData(XmlGetValue(element)); - } - - Ptr ResolveResource(Ptr resource, const WString& path, GuiResourceError::List& errors)override - { - WString text; - if(LoadTextFile(path, text)) + GuiWindow* ownerWindow = dynamic_cast(owner->GetRelatedControlHost()); + if (sharedTooltipOwnerWindow != ownerWindow) { - return new GuiTextData(text); + delete sharedTooltipControl; + sharedTooltipControl = 0; + } + + if(!sharedTooltipControl) + { + sharedTooltipControl = new GuiTooltip(theme::ThemeName::Tooltip); + if (ownerWindow) + { + if (auto tooltipStyle = ownerWindow->GetControlTemplateObject(true)->GetTooltipTemplate()) + { + sharedTooltipControl->SetControlTemplate(tooltipStyle); + } + } + sharedTooltipControl->GetBoundsComposition()->GetEventReceiver()->mouseEnter.AttachMethod(this, &GuiApplication::TooltipMouseEnter); + sharedTooltipControl->GetBoundsComposition()->GetEventReceiver()->mouseLeave.AttachMethod(this, &GuiApplication::TooltipMouseLeave); + } + + sharedTooltipHovering=false; + sharedTooltipClosing=false; + sharedTooltipOwnerWindow = ownerWindow; + sharedTooltipOwner=owner; + sharedTooltipControl->SetTemporaryContentControl(tooltip); + sharedTooltipControl->SetPreferredContentWidth(preferredContentWidth); + sharedTooltipControl->SetClientSize(Size(10, 10)); + sharedTooltipControl->ShowPopup(owner, location); + } + + void GuiApplication::CloseTooltip() + { + if(sharedTooltipControl) + { + if(sharedTooltipHovering) + { + sharedTooltipClosing=true; + } + else + { + sharedTooltipClosing=false; + sharedTooltipControl->Close(); + } + } + } + + GuiControl* GuiApplication::GetTooltipOwner() + { + if(!sharedTooltipControl) return 0; + if(!sharedTooltipControl->GetTemporaryContentControl()) return 0; + return sharedTooltipOwner; + } + + WString GuiApplication::GetExecutablePath() + { + return GetCurrentController()->GetExecutablePath(); + } + + WString GuiApplication::GetExecutableFolder() + { + WString path=GetExecutablePath(); + for(vint i=path.Length()-1;i>=0;i--) + { + if(path[i]==L'\\' || path[i]==L'/') + { + return path.Sub(0, i+1); + } + } + return L""; + } + + bool GuiApplication::IsInMainThread(GuiControlHost* controlHost) + { + return GetCurrentController()->AsyncService()->IsInMainThread(GetThreadContextNativeWindow(controlHost)); + } + + void GuiApplication::InvokeAsync(const Func& proc) + { + GetCurrentController()->AsyncService()->InvokeAsync(proc); + } + + void GuiApplication::InvokeInMainThread(GuiControlHost* controlHost, const Func& proc) + { + GetCurrentController()->AsyncService()->InvokeInMainThread(GetThreadContextNativeWindow(controlHost), proc); + } + + bool GuiApplication::InvokeInMainThreadAndWait(GuiControlHost* controlHost, const Func& proc, vint milliseconds) + { + CHECK_ERROR(!IsInMainThread(controlHost), L"GuiApplication::InvokeInMainThreadAndWait(GuiControlHost*, const Func&, vint)#This function cannot be called in UI thread."); + return GetCurrentController()->AsyncService()->InvokeInMainThreadAndWait(GetThreadContextNativeWindow(controlHost), proc, milliseconds); + } + + Ptr GuiApplication::DelayExecute(const Func& proc, vint milliseconds) + { + return GetCurrentController()->AsyncService()->DelayExecute(proc, milliseconds); + } + + Ptr GuiApplication::DelayExecuteInMainThread(const Func& proc, vint milliseconds) + { + return GetCurrentController()->AsyncService()->DelayExecuteInMainThread(proc, milliseconds); + } + + void GuiApplication::RunGuiTask(GuiControlHost* controlHost, const Func& proc) + { + if(IsInMainThread(controlHost)) + { + return proc(); + } + else + { + InvokeInMainThreadAndWait(controlHost, [&proc]() + { + proc(); + }); + } + } + +/*********************************************************************** +GuiPluginManager +***********************************************************************/ + + class GuiPluginManager : public Object, public IGuiPluginManager + { + protected: + List> plugins; + bool loaded; + public: + GuiPluginManager() + :loaded(false) + { + } + + ~GuiPluginManager() + { + Unload(); + } + + void AddPlugin(Ptr plugin)override + { + CHECK_ERROR(!loaded, L"GuiPluginManager::AddPlugin(Ptr)#Load function has already been executed."); + auto name = plugin->GetName(); + if (name != L"") + { + FOREACH(Ptr, plugin, plugins) + { + CHECK_ERROR(plugin->GetName() != name, L"GuiPluginManager::AddPlugin(Ptr)#Duplicated plugin name."); + } + } + plugins.Add(plugin); + } + + void Load()override + { + CHECK_ERROR(!loaded, L"GuiPluginManager::AddPlugin(Ptr)#Load function has already been executed."); + loaded=true; + + SortedList loaded; + Group loading; + Dictionary> pluginsToLoad; + FOREACH(Ptr, plugin, plugins) + { + auto name = plugin->GetName(); + pluginsToLoad.Add(name, plugin); + List dependencies; + plugin->GetDependencies(dependencies); + FOREACH(WString, dependency, dependencies) + { + loading.Add(name, dependency); + } + } + + while (pluginsToLoad.Count() > 0) + { + vint count = pluginsToLoad.Count(); + { + FOREACH_INDEXER(WString, name, index, pluginsToLoad.Keys()) + { + if (!loading.Keys().Contains(name)) + { + for (vint i = loading.Count() - 1; i >= 0; i--) + { + loading.Remove(loading.Keys()[i], name); + } + loaded.Add(name); + + auto plugin = pluginsToLoad.Values()[index]; + pluginsToLoad.Remove(name); + plugin->Load(); + break; + } + } + } + if (count == pluginsToLoad.Count()) + { + WString message; + FOREACH(Ptr, plugin, pluginsToLoad.Values()) + { + message += L"Cannot load plugin \"" + plugin->GetName() + L"\" because part of its dependencies are not ready:"; + List dependencies; + plugin->GetDependencies(dependencies); + bool first = true; + FOREACH(WString, dependency, dependencies) + { + if (!loaded.Contains(dependency)) + { + message += L" \"" + dependency + L"\";"; + } + } + message += L"\r\n"; + } + throw Exception(message); + } + } + } + + void Unload()override + { + CHECK_ERROR(loaded, L"GuiPluginManager::AddPlugin(Ptr)#Load function has not been executed."); + loaded=false; + FOREACH(Ptr, plugin, plugins) + { + plugin->Unload(); + } + } + + bool IsLoaded()override + { + return loaded; + } + }; + +/*********************************************************************** +Helpers +***********************************************************************/ + + GuiApplication* application=0; + IGuiPluginManager* pluginManager=0; + + GuiApplication* GetApplication() + { + return application; + } + + IGuiPluginManager* GetPluginManager() + { + if(!pluginManager) + { + pluginManager=new GuiPluginManager; + } + return pluginManager; + } + + void DestroyPluginManager() + { + if(pluginManager) + { + delete pluginManager; + pluginManager=0; + } + } + +/*********************************************************************** +GuiApplicationMain +***********************************************************************/ + + class UIThreadAsyncScheduler :public Object, public IAsyncScheduler, public Description + { + public: + void Execute(const Func& callback)override + { + GetApplication()->InvokeInMainThread(GetApplication()->GetMainWindow(), callback); + } + + void ExecuteInBackground(const Func& callback)override + { + GetApplication()->InvokeAsync(callback); + } + + void DelayExecute(const Func& callback, vint milliseconds)override + { + GetApplication()->DelayExecuteInMainThread(callback, milliseconds); + } + }; + + class OtherThreadAsyncScheduler :public Object, public IAsyncScheduler, public Description + { + public: + void Execute(const Func& callback)override + { + GetApplication()->InvokeAsync(callback); + } + + void ExecuteInBackground(const Func& callback)override + { + GetApplication()->InvokeAsync(callback); + } + + void DelayExecute(const Func& callback, vint milliseconds)override + { + GetApplication()->DelayExecute(callback, milliseconds); + } + }; + + void GuiApplicationInitialize() + { + GetCurrentController()->InputService()->StartTimer(); + theme::InitializeTheme(); + +#ifndef VCZH_DEBUG_NO_REFLECTION + GetGlobalTypeManager()->Load(); +#endif + GetPluginManager()->Load(); + + { + GuiApplication app; + application = &app; + IAsyncScheduler::RegisterSchedulerForCurrentThread(new UIThreadAsyncScheduler); + IAsyncScheduler::RegisterDefaultScheduler(new OtherThreadAsyncScheduler); + GuiMain(); + IAsyncScheduler::UnregisterDefaultScheduler(); + IAsyncScheduler::UnregisterSchedulerForCurrentThread(); + } + application = nullptr; + + DestroyPluginManager(); + theme::FinalizeTheme(); + ThreadLocalStorage::DisposeStorages(); + FinalizeGlobalStorage(); +#ifndef VCZH_DEBUG_NO_REFLECTION + DestroyGlobalTypeManager(); +#endif + } + } + } +} + +void GuiApplicationMain() +{ + vl::presentation::controls::GuiApplicationInitialize(); +} + +/*********************************************************************** +.\CONTROLS\GUIBASICCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace compositions; + using namespace collections; + using namespace reflection::description; + +/*********************************************************************** +GuiControl +***********************************************************************/ + + void GuiControl::BeforeControlTemplateUninstalled() + { + } + + void GuiControl::AfterControlTemplateInstalled(bool initialize) + { + controlTemplateObject->SetText(text); + controlTemplateObject->SetFont(font); + controlTemplateObject->SetContext(context); + controlTemplateObject->SetVisuallyEnabled(isVisuallyEnabled); + controlTemplateObject->SetFocusableComposition(focusableComposition); + } + + void GuiControl::CheckAndStoreControlTemplate(templates::GuiControlTemplate* value) + { + controlTemplateObject = value; + } + + void GuiControl::EnsureControlTemplateExists() + { + if (!controlTemplateObject) + { + RebuildControlTemplate(); + } + } + + void GuiControl::RebuildControlTemplate() + { + bool initialize = controlTemplateObject == nullptr; + if (controlTemplateObject) + { + BeforeControlTemplateUninstalled(); + containerComposition->GetParent()->RemoveChild(containerComposition); + boundsComposition->AddChild(containerComposition); + SafeDeleteComposition(controlTemplateObject); + controlTemplateObject = nullptr; + } + + if (controlTemplate) + { + CheckAndStoreControlTemplate(controlTemplate({})); + } + else + { + CheckAndStoreControlTemplate(theme::GetCurrentTheme()->CreateStyle(controlThemeName)({})); + } + + if (controlTemplateObject) + { + controlTemplateObject->SetAlignmentToParent(Margin(0, 0, 0, 0)); + + containerComposition->GetParent()->RemoveChild(containerComposition); + boundsComposition->AddChild(controlTemplateObject); + controlTemplateObject->GetContainerComposition()->AddChild(containerComposition); + AfterControlTemplateInstalled(initialize); + } + } + + void GuiControl::OnChildInserted(GuiControl* control) + { + GuiControl* oldParent=control->parent; + children.Add(control); + control->parent=this; + control->OnParentChanged(oldParent, control->parent); + control->UpdateVisuallyEnabled(); + } + + void GuiControl::OnChildRemoved(GuiControl* control) + { + GuiControl* oldParent=control->parent; + control->parent=0; + children.Remove(control); + control->OnParentChanged(oldParent, control->parent); + } + + void GuiControl::OnParentChanged(GuiControl* oldParent, GuiControl* newParent) + { + OnParentLineChanged(); + } + + void GuiControl::OnParentLineChanged() + { + for(vint i=0;iOnParentLineChanged(); + } + } + + void GuiControl::OnRenderTargetChanged(elements::IGuiGraphicsRenderTarget* renderTarget) + { + RenderTargetChanged.Execute(GetNotifyEventArguments()); + } + + void GuiControl::OnBeforeReleaseGraphicsHost() + { + for(vint i=0;iOnBeforeReleaseGraphicsHost(); + } + } + + void GuiControl::UpdateVisuallyEnabled() + { + bool newValue = isEnabled && (parent == 0 ? true : parent->GetVisuallyEnabled()); + if (isVisuallyEnabled != newValue) + { + isVisuallyEnabled = newValue; + if (controlTemplateObject) + { + controlTemplateObject->SetVisuallyEnabled(isVisuallyEnabled); + } + VisuallyEnabledChanged.Execute(GetNotifyEventArguments()); + + for (vint i = 0; i < children.Count(); i++) + { + children[i]->UpdateVisuallyEnabled(); + } + } + } + + void GuiControl::SetFocusableComposition(compositions::GuiGraphicsComposition* value) + { + if (focusableComposition != value) + { + focusableComposition = value; + if (controlTemplateObject) + { + controlTemplateObject->SetFocusableComposition(focusableComposition); + } + } + } + + bool GuiControl::IsAltEnabled() + { + GuiControl* control = this; + while (control) + { + if (!control->GetVisible() || !control->GetEnabled()) + { + return false; + } + control = control->GetParent(); + } + + return true; + } + + bool GuiControl::IsAltAvailable() + { + return focusableComposition != 0 && alt != L""; + } + + compositions::GuiGraphicsComposition* GuiControl::GetAltComposition() + { + return boundsComposition; + } + + compositions::IGuiAltActionHost* GuiControl::GetActivatingAltHost() + { + return activatingAltHost; + } + + void GuiControl::OnActiveAlt() + { + SetFocus(); + } + + bool GuiControl::SharedPtrDestructorProc(DescriptableObject* obj, bool forceDisposing) + { + GuiControl* value=dynamic_cast(obj); + if(value->GetBoundsComposition()->GetParent()) + { + if (!forceDisposing) return false; + } + SafeDeleteControl(value); + return true; + } + + GuiControl::GuiControl(theme::ThemeName themeName) + :controlThemeName(themeName) + , flagDisposed(new bool(false)) + { + { + boundsComposition = new GuiBoundsComposition; + boundsComposition->SetAssociatedControl(this); + boundsComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + containerComposition = new GuiBoundsComposition; + containerComposition->SetTransparentToMouse(true); + containerComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + containerComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + + boundsComposition->AddChild(containerComposition); + } + { + ControlThemeNameChanged.SetAssociatedComposition(boundsComposition); + ControlTemplateChanged.SetAssociatedComposition(boundsComposition); + RenderTargetChanged.SetAssociatedComposition(boundsComposition); + VisibleChanged.SetAssociatedComposition(boundsComposition); + EnabledChanged.SetAssociatedComposition(boundsComposition); + VisuallyEnabledChanged.SetAssociatedComposition(boundsComposition); + AltChanged.SetAssociatedComposition(boundsComposition); + TextChanged.SetAssociatedComposition(boundsComposition); + FontChanged.SetAssociatedComposition(boundsComposition); + ContextChanged.SetAssociatedComposition(boundsComposition); + } + font = GetCurrentController()->ResourceService()->GetDefaultFont(); + sharedPtrDestructorProc = &GuiControl::SharedPtrDestructorProc; + } + + GuiControl::~GuiControl() + { + *flagDisposed.Obj() = true; + // prevent a root bounds composition from notifying its dead controls + if (!parent) + { + NotifyFinalizeInstance(boundsComposition); + } + + if (tooltipControl) + { + // the only legal parent is the GuiApplication::sharedTooltipWindow + if (tooltipControl->GetBoundsComposition()->GetParent()) + { + tooltipControl->GetBoundsComposition()->GetParent()->RemoveChild(tooltipControl->GetBoundsComposition()); + } + delete tooltipControl; + } + + for (vint i = 0; i < children.Count(); i++) + { + delete children[i]; + } + children.Clear(); + + // let the root control of a control tree delete the whole composition tree + if (!parent) + { + delete boundsComposition; + } + } + + void GuiControl::InvokeOrDelayIfRendering(Func proc) + { + auto controlHost = GetRelatedControlHost(); + if (controlHost && boundsComposition->IsRendering()) + { + auto flag = flagDisposed; + GetApplication()->InvokeInMainThread(controlHost, [=]() + { + if (!*flag.Obj()) + { + proc(); + } + }); + } + else + { + proc(); + } + } + + compositions::GuiEventArgs GuiControl::GetNotifyEventArguments() + { + return GuiEventArgs(boundsComposition); + } + + theme::ThemeName GuiControl::GetControlThemeName() + { + return controlThemeName; + } + + void GuiControl::SetControlThemeName(theme::ThemeName value) + { + SetControlThemeNameAndTemplate(value, controlTemplate); + } + + GuiControl::ControlTemplatePropertyType GuiControl::GetControlTemplate() + { + return controlTemplate; + } + + void GuiControl::SetControlTemplate(const ControlTemplatePropertyType& value) + { + SetControlThemeNameAndTemplate(controlThemeName, value); + } + + void GuiControl::SetControlThemeNameAndTemplate(theme::ThemeName themeNameValue, const ControlTemplatePropertyType& controlTemplateValue) + { + bool themeChanged = (controlThemeName != themeNameValue); + bool templateChanged = (controlTemplate || controlTemplateValue); + if (themeChanged || templateChanged) + { + controlThemeName = themeNameValue; + controlTemplate = controlTemplateValue; + RebuildControlTemplate(); + + if (themeChanged) + { + ControlThemeNameChanged.Execute(GetNotifyEventArguments()); + } + if (templateChanged) + { + ControlTemplateChanged.Execute(GetNotifyEventArguments()); + } + } + } + + templates::GuiControlTemplate* GuiControl::GetControlTemplateObject() + { + EnsureControlTemplateExists(); + return controlTemplateObject; + } + + compositions::GuiBoundsComposition* GuiControl::GetBoundsComposition() + { + EnsureControlTemplateExists(); + return boundsComposition; + } + + compositions::GuiGraphicsComposition* GuiControl::GetContainerComposition() + { + EnsureControlTemplateExists(); + return containerComposition; + } + + compositions::GuiGraphicsComposition* GuiControl::GetFocusableComposition() + { + EnsureControlTemplateExists(); + return focusableComposition; + } + + GuiControl* GuiControl::GetParent() + { + return parent; + } + + vint GuiControl::GetChildrenCount() + { + return children.Count(); + } + + GuiControl* GuiControl::GetChild(vint index) + { + return children[index]; + } + + bool GuiControl::AddChild(GuiControl* control) + { + return GetContainerComposition()->AddChild(control->GetBoundsComposition()); + } + + bool GuiControl::HasChild(GuiControl* control) + { + return children.Contains(control); + } + + GuiControlHost* GuiControl::GetRelatedControlHost() + { + return parent?parent->GetRelatedControlHost():0; + } + + bool GuiControl::GetVisuallyEnabled() + { + return isVisuallyEnabled; + } + + bool GuiControl::GetEnabled() + { + return isEnabled; + } + + void GuiControl::SetEnabled(bool value) + { + if(isEnabled!=value) + { + isEnabled=value; + EnabledChanged.Execute(GetNotifyEventArguments()); + UpdateVisuallyEnabled(); + } + } + + bool GuiControl::GetVisible() + { + return isVisible; + } + + void GuiControl::SetVisible(bool value) + { + boundsComposition->SetVisible(value); + if(isVisible!=value) + { + isVisible=value; + VisibleChanged.Execute(GetNotifyEventArguments()); + } + } + + const WString& GuiControl::GetAlt() + { + return alt; + } + + bool GuiControl::SetAlt(const WString& value) + { + if (!IGuiAltAction::IsLegalAlt(value)) return false; + if (alt != value) + { + alt = value; + AltChanged.Execute(GetNotifyEventArguments()); + } + return true; + } + + void GuiControl::SetActivatingAltHost(compositions::IGuiAltActionHost* host) + { + activatingAltHost = host; + } + + const WString& GuiControl::GetText() + { + return text; + } + + void GuiControl::SetText(const WString& value) + { + if (text != value) + { + text = value; + if (controlTemplateObject) + { + controlTemplateObject->SetText(text); + } + TextChanged.Execute(GetNotifyEventArguments()); + } + } + + const FontProperties& GuiControl::GetFont() + { + return font; + } + + void GuiControl::SetFont(const FontProperties& value) + { + if (font != value) + { + font = value; + if (controlTemplateObject) + { + controlTemplateObject->SetFont(font); + } + FontChanged.Execute(GetNotifyEventArguments()); + } + } + + description::Value GuiControl::GetContext() + { + return context; + } + + void GuiControl::SetContext(const description::Value& value) + { + if (context != value) + { + context = value; + if (controlTemplateObject) + { + controlTemplateObject->SetContext(context); + } + ContextChanged.Execute(GetNotifyEventArguments()); + } + } + + void GuiControl::SetFocus() + { + if(focusableComposition) + { + GuiGraphicsHost* host=focusableComposition->GetRelatedGraphicsHost(); + if(host) + { + host->SetFocus(focusableComposition); + } + } + } + + description::Value GuiControl::GetTag() + { + return tag; + } + + void GuiControl::SetTag(const description::Value& value) + { + tag=value; + } + + GuiControl* GuiControl::GetTooltipControl() + { + return tooltipControl; + } + + GuiControl* GuiControl::SetTooltipControl(GuiControl* value) + { + GuiControl* oldTooltipControl=tooltipControl; + tooltipControl=value; + return oldTooltipControl; + } + + vint GuiControl::GetTooltipWidth() + { + return tooltipWidth; + } + + void GuiControl::SetTooltipWidth(vint value) + { + tooltipWidth=value; + } + + bool GuiControl::DisplayTooltip(Point location) + { + if(!tooltipControl) return false; + GetApplication()->ShowTooltip(this, tooltipControl, tooltipWidth, location); + return true; + } + + void GuiControl::CloseTooltip() + { + if(GetApplication()->GetTooltipOwner()==this) + { + GetApplication()->CloseTooltip(); + } + } + + IDescriptable* GuiControl::QueryService(const WString& identifier) + { + if (identifier == IGuiAltAction::Identifier) + { + return (IGuiAltAction*)this; + } + else if (identifier == IGuiAltActionContainer::Identifier) + { + return 0; + } + else if(parent) + { + return parent->QueryService(identifier); } else { - errors.Add(GuiResourceError({ resource }, L"Failed to load file \"" + path + L"\".")); return 0; } } - Ptr ResolveResourcePrecompiled(Ptr resource, stream::IStream& stream, GuiResourceError::List& errors)override - { - stream::internal::ContextFreeReader reader(stream); - WString text; - reader << text; - return new GuiTextData(text); - } - }; - /*********************************************************************** -Xml Type Resolver (Xml) +GuiCustomControl ***********************************************************************/ - class GuiResourceXmlTypeResolver - : public Object - , public IGuiResourceTypeResolver - , private IGuiResourceTypeResolver_DirectLoadXml - , private IGuiResourceTypeResolver_DirectLoadStream + controls::GuiControlHost* GuiCustomControl::GetControlHostForInstance() + { + return GetRelatedControlHost(); + } + + void GuiCustomControl::OnParentLineChanged() + { + GuiControl::OnParentLineChanged(); + OnControlHostForInstanceChanged(); + } + + GuiCustomControl::GuiCustomControl(theme::ThemeName themeName) + :GuiControl(themeName) + { + } + + GuiCustomControl::~GuiCustomControl() + { + FinalizeAggregation(); + FinalizeInstanceRecursively(this); + } + } + } +} + +/*********************************************************************** +.\CONTROLS\GUIBUTTONCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls { - public: - WString GetType()override + using namespace elements; + using namespace compositions; + using namespace collections; + using namespace reflection::description; + +/*********************************************************************** +GuiButton +***********************************************************************/ + + void GuiButton::BeforeControlTemplateUninstalled_() { - return L"Xml"; } - bool XmlSerializable()override + void GuiButton::AfterControlTemplateInstalled_(bool initialize) { - return true; + auto ct = GetControlTemplateObject(true); + GetControlTemplateObject(true)->SetState(controlState); } - bool StreamSerializable()override + void GuiButton::OnParentLineChanged() { - return true; - } - - IGuiResourceTypeResolver_DirectLoadXml* DirectLoadXml()override - { - return this; - } - - IGuiResourceTypeResolver_DirectLoadStream* DirectLoadStream()override - { - return this; - } - - Ptr Serialize(Ptr resource, Ptr content)override - { - if (auto obj = content.Cast()) + GuiControl::OnParentLineChanged(); + if(GetRelatedControlHost()==0) { - auto xmlXml = MakePtr(); - xmlXml->name.value = L"Xml"; - xmlXml->subNodes.Add(obj->rootElement); - return xmlXml; - } - return nullptr; - } - - void SerializePrecompiled(Ptr resource, Ptr content, stream::IStream& stream)override - { - auto obj = content.Cast(); - MemoryStream buffer; - { - StreamWriter writer(buffer); - XmlPrint(obj, writer); - } - { - buffer.SeekFromBegin(0); - StreamReader reader(buffer); - WString text = reader.ReadToEnd(); - - stream::internal::ContextFreeWriter writer(stream); - writer << text; + mousePressing=false; + mouseHoving=false; + UpdateControlState(); } } - Ptr ResolveResource(Ptr resource, Ptr element, GuiResourceError::List& errors)override + void GuiButton::OnActiveAlt() { - Ptr root = XmlGetElements(element).First(0); - if(root) - { - Ptr xml=new XmlDocument; - xml->rootElement=root; - return xml; - } - return nullptr; + Clicked.Execute(GetNotifyEventArguments()); } - Ptr ResolveResource(Ptr resource, const WString& path, GuiResourceError::List& errors)override + void GuiButton::UpdateControlState() { - if (auto parser = GetParserManager()->GetParser(L"XML")) + auto newControlState = ButtonState::Normal; + if (mousePressing) { - WString text; - if (LoadTextFile(path, text)) + if (mouseHoving) { - return parser->Parse({ resource }, text, errors); + newControlState = ButtonState::Pressed; } else { - errors.Add(GuiResourceError({ resource }, L"Failed to load file \"" + path + L"\".")); + newControlState = ButtonState::Active; + } + } + else + { + if (mouseHoving) + { + newControlState = ButtonState::Active; + } + else + { + newControlState = ButtonState::Normal; + } + } + if (controlState != newControlState) + { + controlState = newControlState; + GetControlTemplateObject(true)->SetState(controlState); + } + } + + void GuiButton::OnLeftButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(arguments.eventSource==boundsComposition) + { + mousePressing=true; + boundsComposition->GetRelatedGraphicsHost()->SetFocus(boundsComposition); + UpdateControlState(); + if(!clickOnMouseUp && arguments.eventSource->GetAssociatedControl()==this) + { + Clicked.Execute(GetNotifyEventArguments()); + } + } + } + + void GuiButton::OnLeftButtonUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(arguments.eventSource==boundsComposition) + { + mousePressing=false; + UpdateControlState(); + } + if(GetVisuallyEnabled()) + { + if(mouseHoving && clickOnMouseUp) + { + auto eventSource = arguments.eventSource->GetAssociatedControl(); + while (eventSource && eventSource != this) + { + if (eventSource->GetFocusableComposition()) + { + return; + } + eventSource = eventSource->GetParent(); + } + Clicked.Execute(GetNotifyEventArguments()); + } + } + } + + void GuiButton::OnMouseEnter(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(arguments.eventSource==boundsComposition) + { + mouseHoving=true; + UpdateControlState(); + } + } + + void GuiButton::OnMouseLeave(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(arguments.eventSource==boundsComposition) + { + mouseHoving=false; + UpdateControlState(); + } + } + + GuiButton::GuiButton(theme::ThemeName themeName) + :GuiControl(themeName) + { + Clicked.SetAssociatedComposition(boundsComposition); + SetFocusableComposition(boundsComposition); + + boundsComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiButton::OnLeftButtonDown); + boundsComposition->GetEventReceiver()->leftButtonUp.AttachMethod(this, &GuiButton::OnLeftButtonUp); + boundsComposition->GetEventReceiver()->mouseEnter.AttachMethod(this, &GuiButton::OnMouseEnter); + boundsComposition->GetEventReceiver()->mouseLeave.AttachMethod(this, &GuiButton::OnMouseLeave); + } + + GuiButton::~GuiButton() + { + } + + bool GuiButton::GetClickOnMouseUp() + { + return clickOnMouseUp; + } + + void GuiButton::SetClickOnMouseUp(bool value) + { + clickOnMouseUp=value; + } + +/*********************************************************************** +GuiSelectableButton::GroupController +***********************************************************************/ + + GuiSelectableButton::GroupController::GroupController() + { + } + + GuiSelectableButton::GroupController::~GroupController() + { + for(vint i=buttons.Count()-1;i>=0;i--) + { + buttons[i]->SetGroupController(0); + } + } + + void GuiSelectableButton::GroupController::Attach(GuiSelectableButton* button) + { + if(!buttons.Contains(button)) + { + buttons.Add(button); + } + } + + void GuiSelectableButton::GroupController::Detach(GuiSelectableButton* button) + { + buttons.Remove(button); + } + +/*********************************************************************** +GuiSelectableButton::MutexGroupController +***********************************************************************/ + + GuiSelectableButton::MutexGroupController::MutexGroupController() + :suppress(false) + { + } + + GuiSelectableButton::MutexGroupController::~MutexGroupController() + { + } + + void GuiSelectableButton::MutexGroupController::OnSelectedChanged(GuiSelectableButton* button) + { + if(!suppress) + { + suppress=true; + for(vint i=0;iSetSelected(buttons[i]==button); + } + suppress=false; + } + } + +/*********************************************************************** +GuiSelectableButton +***********************************************************************/ + + void GuiSelectableButton::BeforeControlTemplateUninstalled_() + { + } + + void GuiSelectableButton::AfterControlTemplateInstalled_(bool initialize) + { + GetControlTemplateObject(true)->SetSelected(isSelected); + } + + void GuiSelectableButton::OnClicked(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(autoSelection) + { + SetSelected(!GetSelected()); + } + } + + GuiSelectableButton::GuiSelectableButton(theme::ThemeName themeName) + :GuiButton(themeName) + { + GroupControllerChanged.SetAssociatedComposition(boundsComposition); + AutoSelectionChanged.SetAssociatedComposition(boundsComposition); + SelectedChanged.SetAssociatedComposition(boundsComposition); + + Clicked.AttachMethod(this, &GuiSelectableButton::OnClicked); + } + + GuiSelectableButton::~GuiSelectableButton() + { + if(groupController) + { + groupController->Detach(this); + } + } + + GuiSelectableButton::GroupController* GuiSelectableButton::GetGroupController() + { + return groupController; + } + + void GuiSelectableButton::SetGroupController(GroupController* value) + { + if(groupController) + { + groupController->Detach(this); + } + groupController=value; + if(groupController) + { + groupController->Attach(this); + } + GroupControllerChanged.Execute(GetNotifyEventArguments()); + } + + bool GuiSelectableButton::GetAutoSelection() + { + return autoSelection; + } + + void GuiSelectableButton::SetAutoSelection(bool value) + { + if(autoSelection!=value) + { + autoSelection=value; + AutoSelectionChanged.Execute(GetNotifyEventArguments()); + } + } + + bool GuiSelectableButton::GetSelected() + { + return isSelected; + } + + void GuiSelectableButton::SetSelected(bool value) + { + if (isSelected != value) + { + isSelected = value; + GetControlTemplateObject(true)->SetSelected(isSelected); + if (groupController) + { + groupController->OnSelectedChanged(this); + } + SelectedChanged.Execute(GetNotifyEventArguments()); + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\GUICONTAINERCONTROLS.CPP +***********************************************************************/ + + +namespace vl +{ + namespace presentation + { + using namespace compositions; + + namespace controls + { + +/*********************************************************************** +GuiTabPage +***********************************************************************/ + + bool GuiTabPage::IsAltAvailable() + { + return false; + } + + GuiTabPage::GuiTabPage(theme::ThemeName themeName) + :GuiCustomControl(themeName) + { + } + + GuiTabPage::~GuiTabPage() + { + FinalizeAggregation(); + } + + GuiTab* GuiTabPage::GetOwnerTab() + { + return tab; + } + +/*********************************************************************** +GuiTabPageList +***********************************************************************/ + + bool GuiTabPageList::QueryInsert(vint index, GuiTabPage* const& value) + { + return !items.Contains(value) && value->tab == nullptr; + } + + void GuiTabPageList::AfterInsert(vint index, GuiTabPage* const& value) + { + value->tab = tab; + value->SetVisible(false); + value->boundsComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + tab->containerComposition->AddChild(value->boundsComposition); + + if (!tab->selectedPage) + { + tab->SetSelectedPage(value); + } + } + + void GuiTabPageList::BeforeRemove(vint index, GuiTabPage* const& value) + { + tab->containerComposition->RemoveChild(value->boundsComposition); + value->tab = nullptr; + + if (items.Count() == 0) + { + tab->SetSelectedPage(nullptr); + } + else if (tab->selectedPage == value) + { + tab->SetSelectedPage(items[0]); + } + } + + GuiTabPageList::GuiTabPageList(GuiTab* _tab) + :tab(_tab) + { + } + + GuiTabPageList::~GuiTabPageList() + { + } + +/*********************************************************************** +GuiTab +***********************************************************************/ + + void GuiTab::BeforeControlTemplateUninstalled_() + { + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + ct->SetCommands(nullptr); + ct->SetTabPages(nullptr); + ct->SetSelectedTabPage(nullptr); + } + + void GuiTab::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + ct->SetCommands(commandExecutor.Obj()); + ct->SetTabPages(tabPages.GetWrapper()); + ct->SetSelectedTabPage(selectedPage); + } + + GuiTab::CommandExecutor::CommandExecutor(GuiTab* _tab) + :tab(_tab) + { + } + + GuiTab::CommandExecutor::~CommandExecutor() + { + } + + void GuiTab::CommandExecutor::ShowTab(vint index) + { + tab->SetSelectedPage(tab->GetPages().Get(index)); + } + + GuiTab::GuiTab(theme::ThemeName themeName) + :GuiControl(themeName) + , tabPages(this) + { + commandExecutor = new CommandExecutor(this); + } + + GuiTab::~GuiTab() + { + } + + collections::ObservableList& GuiTab::GetPages() + { + return tabPages; + } + + GuiTabPage* GuiTab::GetSelectedPage() + { + return selectedPage; + } + + bool GuiTab::SetSelectedPage(GuiTabPage* value) + { + if (!value) + { + if (tabPages.Count() == 0) + { + selectedPage = nullptr; + } + } + else if (value->GetOwnerTab() == this) + { + if (selectedPage == value) + { + return true; + } + + selectedPage = value; + FOREACH(GuiTabPage*, tabPage, tabPages) + { + tabPage->SetVisible(tabPage == selectedPage); + } + } + if (auto ct = GetControlTemplateObject(false)) + { + ct->SetSelectedTabPage(selectedPage); + } + SelectedPageChanged.Execute(GetNotifyEventArguments()); + return selectedPage == value; + } + +/*********************************************************************** +GuiScrollView +***********************************************************************/ + + void GuiScrollView::BeforeControlTemplateUninstalled_() + { + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + if (auto scroll = ct->GetHorizontalScroll()) + { + scroll->PositionChanged.Detach(hScrollHandler); + } + if (auto scroll = ct->GetVerticalScroll()) + { + scroll->PositionChanged.Detach(vScrollHandler); + } + ct->GetEventReceiver()->horizontalWheel.Detach(hWheelHandler); + ct->GetEventReceiver()->verticalWheel.Detach(vWheelHandler); + ct->BoundsChanged.Detach(containerBoundsChangedHandler); + + hScrollHandler = nullptr; + vScrollHandler = nullptr; + hWheelHandler = nullptr; + vWheelHandler = nullptr; + containerBoundsChangedHandler = nullptr; + supressScrolling = false; + } + + void GuiScrollView::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + if (auto scroll = ct->GetHorizontalScroll()) + { + hScrollHandler = scroll->PositionChanged.AttachMethod(this, &GuiScrollView::OnHorizontalScroll); + } + if (auto scroll = ct->GetVerticalScroll()) + { + vScrollHandler = scroll->PositionChanged.AttachMethod(this, &GuiScrollView::OnVerticalScroll); + } + hWheelHandler = ct->GetEventReceiver()->horizontalWheel.AttachMethod(this, &GuiScrollView::OnHorizontalWheel); + vWheelHandler = ct->GetEventReceiver()->verticalWheel.AttachMethod(this, &GuiScrollView::OnVerticalWheel); + containerBoundsChangedHandler = ct->BoundsChanged.AttachMethod(this, &GuiScrollView::OnContainerBoundsChanged); + CalculateView(); + } + + void GuiScrollView::OnContainerBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + InvokeOrDelayIfRendering([=]() + { + CalculateView(); + }); + } + + void GuiScrollView::OnHorizontalScroll(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(!supressScrolling) + { + CallUpdateView(); + } + } + + void GuiScrollView::OnVerticalScroll(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(!supressScrolling) + { + CallUpdateView(); + } + } + + void GuiScrollView::OnHorizontalWheel(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(!supressScrolling) + { + if (auto scroll = GetControlTemplateObject(true)->GetHorizontalScroll()) + { + vint position = scroll->GetPosition(); + vint move = scroll->GetSmallMove(); + position -= move * arguments.wheel / 60; + scroll->SetPosition(position); + } + } + } + + void GuiScrollView::OnVerticalWheel(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(!supressScrolling && GetVisuallyEnabled()) + { + if (auto scroll = GetControlTemplateObject(true)->GetVerticalScroll()) + { + vint position = scroll->GetPosition(); + vint move = scroll->GetSmallMove(); + position -= move * arguments.wheel / 60; + scroll->SetPosition(position); + } + } + } + + void GuiScrollView::CallUpdateView() + { + Rect viewBounds=GetViewBounds(); + UpdateView(viewBounds); + } + + bool GuiScrollView::AdjustView(Size fullSize) + { + auto ct = GetControlTemplateObject(true); + auto hScroll = ct->GetHorizontalScroll(); + auto vScroll = ct->GetVerticalScroll(); + Size viewSize = ct->GetContainerComposition()->GetBounds().GetSize(); + + auto hVisible = hScroll ? hScroll->GetVisible() : false; + auto vVisible = vScroll ? vScroll->GetVisible() : false; + + if (hScroll) + { + if (fullSize.x <= viewSize.x) + { + hScroll->SetVisible(horizontalAlwaysVisible); + hScroll->SetEnabled(false); + hScroll->SetPosition(0); + } + else + { + hScroll->SetVisible(true); + hScroll->SetEnabled(true); + hScroll->SetTotalSize(fullSize.x); + hScroll->SetPageSize(viewSize.x); + } + } + + if (vScroll) + { + if (fullSize.y <= viewSize.y) + { + vScroll->SetVisible(verticalAlwaysVisible); + vScroll->SetEnabled(false); + vScroll->SetPosition(0); + } + else + { + vScroll->SetVisible(true); + vScroll->SetEnabled(true); + vScroll->SetTotalSize(fullSize.y); + vScroll->SetPageSize(viewSize.y); + } + } + + auto hVisible2 = hScroll ? hScroll->GetVisible() : false; + auto vVisible2 = vScroll ? vScroll->GetVisible() : false; + return hVisible != hVisible2 || vVisible != vVisible2; + } + + GuiScrollView::GuiScrollView(theme::ThemeName themeName) + :GuiControl(themeName) + { + containerComposition->BoundsChanged.AttachMethod(this, &GuiScrollView::OnContainerBoundsChanged); + } + + vint GuiScrollView::GetSmallMove() + { + return GetFont().size * 2; + } + + Size GuiScrollView::GetBigMove() + { + return GetViewSize(); + } + + GuiScrollView::~GuiScrollView() + { + } + + void GuiScrollView::SetFont(const FontProperties& value) + { + GuiControl::SetFont(value); + CalculateView(); + } + + void GuiScrollView::CalculateView() + { + auto ct = GetControlTemplateObject(true); + if (!supressScrolling) + { + Size fullSize = QueryFullSize(); + while (true) + { + bool flagA = AdjustView(fullSize); + bool flagB = AdjustView(fullSize); + supressScrolling = true; + CallUpdateView(); + supressScrolling = false; + + Size newSize = QueryFullSize(); + if (fullSize == newSize) + { + vint smallMove = GetSmallMove(); + Size bigMove = GetBigMove(); + if (auto scroll = ct->GetHorizontalScroll()) + { + scroll->SetSmallMove(smallMove); + scroll->SetBigMove(bigMove.x); + } + if (auto scroll = ct->GetVerticalScroll()) + { + scroll->SetSmallMove(smallMove); + scroll->SetBigMove(bigMove.y); + } + + if (!flagA && !flagB) + { + break; + } + } + else + { + fullSize = newSize; + } + } + } + } + + Size GuiScrollView::GetViewSize() + { + Size viewSize = GetControlTemplateObject(true)->GetContainerComposition()->GetBounds().GetSize(); + return viewSize; + } + + Rect GuiScrollView::GetViewBounds() + { + return Rect(GetViewPosition(), GetViewSize()); + } + + Point GuiScrollView::GetViewPosition() + { + auto ct = GetControlTemplateObject(true); + auto hScroll = ct->GetHorizontalScroll(); + auto vScroll = ct->GetVerticalScroll(); + return Point(hScroll ? hScroll->GetPosition() : 0, vScroll ? vScroll->GetPosition() : 0); + } + + void GuiScrollView::SetViewPosition(Point value) + { + auto ct = GetControlTemplateObject(true); + if (auto hScroll = ct->GetHorizontalScroll()) + { + hScroll->SetPosition(value.x); + } + if (auto vScroll = ct->GetVerticalScroll()) + { + vScroll->SetPosition(value.y); + } + } + + GuiScroll* GuiScrollView::GetHorizontalScroll() + { + return GetControlTemplateObject(true)->GetHorizontalScroll(); + } + + GuiScroll* GuiScrollView::GetVerticalScroll() + { + return GetControlTemplateObject(true)->GetVerticalScroll(); + } + + bool GuiScrollView::GetHorizontalAlwaysVisible() + { + return horizontalAlwaysVisible; + } + + void GuiScrollView::SetHorizontalAlwaysVisible(bool value) + { + if (horizontalAlwaysVisible != value) + { + horizontalAlwaysVisible = value; + CalculateView(); + } + } + + bool GuiScrollView::GetVerticalAlwaysVisible() + { + return verticalAlwaysVisible; + } + + void GuiScrollView::SetVerticalAlwaysVisible(bool value) + { + if (verticalAlwaysVisible != value) + { + verticalAlwaysVisible = value; + CalculateView(); + } + } + +/*********************************************************************** +GuiScrollContainer +***********************************************************************/ + + Size GuiScrollContainer::QueryFullSize() + { + return containerComposition->GetBounds().GetSize(); + } + + void GuiScrollContainer::UpdateView(Rect viewBounds) + { + auto leftTop = Point(-viewBounds.x1, -viewBounds.y1); + containerComposition->SetBounds(Rect(leftTop, Size(0, 0))); + } + + GuiScrollContainer::GuiScrollContainer(theme::ThemeName themeName) + :GuiScrollView(themeName) + { + containerComposition->SetAlignmentToParent(Margin(-1, -1, -1, -1)); + UpdateView(Rect(0, 0, 0, 0)); + } + + GuiScrollContainer::~GuiScrollContainer() + { + } + + bool GuiScrollContainer::GetExtendToFullWidth() + { + return extendToFullWidth; + } + + void GuiScrollContainer::SetExtendToFullWidth(bool value) + { + if (extendToFullWidth != value) + { + extendToFullWidth = value; + auto margin = containerComposition->GetAlignmentToParent(); + if (value) + { + containerComposition->SetAlignmentToParent(Margin(0, margin.top, 0, margin.bottom)); + } + else + { + containerComposition->SetAlignmentToParent(Margin(-1, margin.top, -1, margin.bottom)); + } + } + } + + bool GuiScrollContainer::GetExtendToFullHeight() + { + return extendToFullHeight; + } + + void GuiScrollContainer::SetExtendToFullHeight(bool value) + { + if (extendToFullHeight != value) + { + extendToFullHeight = value; + auto margin = containerComposition->GetAlignmentToParent(); + if (value) + { + containerComposition->SetAlignmentToParent(Margin(margin.left, 0, margin.right, 0)); + } + else + { + containerComposition->SetAlignmentToParent(Margin(margin.left, -1, margin.right, -1)); + } + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\GUIDATETIMECONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace compositions; + using namespace elements; + +/*********************************************************************** +GuiDatePicker::CommandExecutor +***********************************************************************/ + + GuiDatePicker::CommandExecutor::CommandExecutor(GuiDatePicker* _datePicker) + :datePicker(_datePicker) + { + } + + GuiDatePicker::CommandExecutor::~CommandExecutor() + { + } + + void GuiDatePicker::CommandExecutor::NotifyDateChanged() + { + datePicker->date = datePicker->GetControlTemplateObject(true)->GetDate(); + datePicker->UpdateText(); + datePicker->DateChanged.Execute(datePicker->GetNotifyEventArguments()); + } + + void GuiDatePicker::CommandExecutor::NotifyDateNavigated() + { + datePicker->DateNavigated.Execute(datePicker->GetNotifyEventArguments()); + } + + void GuiDatePicker::CommandExecutor::NotifyDateSelected() + { + datePicker->DateSelected.Execute(datePicker->GetNotifyEventArguments()); + } + +/*********************************************************************** +GuiDatePicker +***********************************************************************/ + + void GuiDatePicker::BeforeControlTemplateUninstalled_() + { + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + ct->SetCommands(nullptr); + } + + void GuiDatePicker::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + ct->SetCommands(commandExecutor.Obj()); + ct->SetDate(date); + ct->SetDateLocale(dateLocale); + UpdateText(); + } + + void GuiDatePicker::UpdateText() + { + GuiControl::SetText(dateLocale.FormatDate(dateFormat, date)); + } + + GuiDatePicker::GuiDatePicker(theme::ThemeName themeName) + :GuiControl(themeName) + { + commandExecutor = new CommandExecutor(this); + SetDateLocale(Locale::UserDefault()); + SetDate(DateTime::LocalTime()); + + DateChanged.SetAssociatedComposition(boundsComposition); + DateNavigated.SetAssociatedComposition(boundsComposition); + DateSelected.SetAssociatedComposition(boundsComposition); + DateFormatChanged.SetAssociatedComposition(boundsComposition); + DateLocaleChanged.SetAssociatedComposition(boundsComposition); + + commandExecutor->NotifyDateChanged(); + } + + GuiDatePicker::~GuiDatePicker() + { + } + + const DateTime& GuiDatePicker::GetDate() + { + return date; + } + + void GuiDatePicker::SetDate(const DateTime& value) + { + if (date != value) + { + date = value; + GetControlTemplateObject(true)->SetDate(value); + } + } + + const WString& GuiDatePicker::GetDateFormat() + { + return dateFormat; + } + + void GuiDatePicker::SetDateFormat(const WString& value) + { + dateFormat=value; + UpdateText(); + DateFormatChanged.Execute(GetNotifyEventArguments()); + } + + const Locale& GuiDatePicker::GetDateLocale() + { + return dateLocale; + } + + void GuiDatePicker::SetDateLocale(const Locale& value) + { + dateLocale=value; + List formats; + dateLocale.GetLongDateFormats(formats); + if(formats.Count()>0) + { + dateFormat=formats[0]; + } + GetControlTemplateObject(true)->SetDateLocale(dateLocale); + + UpdateText(); + DateFormatChanged.Execute(GetNotifyEventArguments()); + DateLocaleChanged.Execute(GetNotifyEventArguments()); + } + + void GuiDatePicker::SetText(const WString& value) + { + } + +/*********************************************************************** +GuiDateComboBox +***********************************************************************/ + + void GuiDateComboBox::UpdateText() + { + SetText(datePicker->GetDateLocale().FormatDate(datePicker->GetDateFormat(), selectedDate)); + } + + void GuiDateComboBox::NotifyUpdateSelectedDate() + { + UpdateText(); + SelectedDateChanged.Execute(GetNotifyEventArguments()); + } + + void GuiDateComboBox::OnSubMenuOpeningChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + datePicker->SetDate(selectedDate); + } + + void GuiDateComboBox::datePicker_DateLocaleChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + UpdateText(); + } + + void GuiDateComboBox::datePicker_DateFormatChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + UpdateText(); + } + + void GuiDateComboBox::datePicker_DateSelected(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + selectedDate=datePicker->GetDate(); + GetSubMenu()->Hide(); + SelectItem(); + NotifyUpdateSelectedDate(); + } + + GuiDateComboBox::GuiDateComboBox(theme::ThemeName themeName, GuiDatePicker* _datePicker) + :GuiComboBoxBase(themeName) + ,datePicker(_datePicker) + { + SelectedDateChanged.SetAssociatedComposition(GetBoundsComposition()); + + datePicker->DateSelected.AttachMethod(this, &GuiDateComboBox::datePicker_DateSelected); + datePicker->DateLocaleChanged.AttachMethod(this, &GuiDateComboBox::datePicker_DateLocaleChanged); + datePicker->DateFormatChanged.AttachMethod(this, &GuiDateComboBox::datePicker_DateFormatChanged); + datePicker->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + GetSubMenu()->GetContainerComposition()->AddChild(datePicker->GetBoundsComposition()); + + selectedDate=datePicker->GetDate(); + SubMenuOpeningChanged.AttachMethod(this, &GuiDateComboBox::OnSubMenuOpeningChanged); + SetFont(GetFont()); + SetText(datePicker->GetText()); + } + + GuiDateComboBox::~GuiDateComboBox() + { + } + + void GuiDateComboBox::SetFont(const FontProperties& value) + { + GuiComboBoxBase::SetFont(value); + datePicker->SetFont(value); + } + + const DateTime& GuiDateComboBox::GetSelectedDate() + { + return selectedDate; + } + + void GuiDateComboBox::SetSelectedDate(const DateTime& value) + { + selectedDate=value; + NotifyUpdateSelectedDate(); + } + + GuiDatePicker* GuiDateComboBox::GetDatePicker() + { + return datePicker; + } + } + } +} + +/*********************************************************************** +.\CONTROLS\GUIDIALOGS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace compositions; + using namespace collections; + using namespace reflection::description; + +/*********************************************************************** +GuiDialogBase +***********************************************************************/ + + GuiWindow* GuiDialogBase::GetHostWindow() + { + if (rootObject) + { + if (auto control = dynamic_cast(rootObject)) + { + if (auto host = control->GetRelatedControlHost()) + { + return dynamic_cast(host); + } + } + else if (auto composition = dynamic_cast(rootObject)) + { + if (auto host = composition->GetRelatedControlHost()) + { + return dynamic_cast(host); + } } } return nullptr; } - Ptr ResolveResourcePrecompiled(Ptr resource, stream::IStream& stream, GuiResourceError::List& errors)override + GuiDialogBase::GuiDialogBase() { - if (auto parser = GetParserManager()->GetParser(L"XML")) - { - stream::internal::ContextFreeReader reader(stream); - WString text; - reader << text; - - return parser->Parse({ resource }, text, errors); - } - return nullptr; } - }; + + GuiDialogBase::~GuiDialogBase() + { + } + + void GuiDialogBase::Attach(GuiInstanceRootObject* _rootObject) + { + rootObject = _rootObject; + } + + void GuiDialogBase::Detach(GuiInstanceRootObject* _rootObject) + { + rootObject = nullptr; + } /*********************************************************************** -Doc Type Resolver (Doc) +GuiMessageDialog ***********************************************************************/ - class GuiResourceDocTypeResolver - : public Object - , public IGuiResourceTypeResolver - , private IGuiResourceTypeResolver_IndirectLoad + GuiMessageDialog::GuiMessageDialog() + { + } + + GuiMessageDialog::~GuiMessageDialog() + { + } + + INativeDialogService::MessageBoxButtonsInput GuiMessageDialog::GetInput() + { + return input; + } + + void GuiMessageDialog::SetInput(INativeDialogService::MessageBoxButtonsInput value) + { + input = value; + } + + INativeDialogService::MessageBoxDefaultButton GuiMessageDialog::GetDefaultButton() + { + return defaultButton; + } + + void GuiMessageDialog::SetDefaultButton(INativeDialogService::MessageBoxDefaultButton value) + { + defaultButton = value; + } + + INativeDialogService::MessageBoxIcons GuiMessageDialog::GetIcon() + { + return icon; + } + + void GuiMessageDialog::SetIcon(INativeDialogService::MessageBoxIcons value) + { + icon = value; + } + + INativeDialogService::MessageBoxModalOptions GuiMessageDialog::GetModalOption() + { + return modalOption; + } + + void GuiMessageDialog::SetModalOption(INativeDialogService::MessageBoxModalOptions value) + { + modalOption = value; + } + + const WString& GuiMessageDialog::GetText() + { + return text; + } + + void GuiMessageDialog::SetText(const WString& value) + { + text = value; + } + + const WString& GuiMessageDialog::GetTitle() + { + return title; + } + + void GuiMessageDialog::SetTitle(const WString& value) + { + title = value; + } + + INativeDialogService::MessageBoxButtonsOutput GuiMessageDialog::ShowDialog() + { + auto service = GetCurrentController()->DialogService(); + return service->ShowMessageBox(GetHostWindow()->GetNativeWindow(), text, title, input, defaultButton, icon, modalOption); + } + +/*********************************************************************** +GuiColorDialog +***********************************************************************/ + + GuiColorDialog::GuiColorDialog() + { + for (vint i = 0; i < 16; i++) + { + customColors.Add(Color()); + } + } + + GuiColorDialog::~GuiColorDialog() + { + } + + bool GuiColorDialog::GetEnabledCustomColor() + { + return enabledCustomColor; + } + + void GuiColorDialog::SetEnabledCustomColor(bool value) + { + enabledCustomColor = value; + } + + bool GuiColorDialog::GetOpenedCustomColor() + { + return openedCustomColor; + } + + void GuiColorDialog::SetOpenedCustomColor(bool value) + { + openedCustomColor = value; + } + + Color GuiColorDialog::GetSelectedColor() + { + return selectedColor; + } + + void GuiColorDialog::SetSelectedColor(Color value) + { + if (selectedColor != value) + { + selectedColor = value; + SelectedColorChanged.Execute(GuiEventArgs()); + } + } + + collections::List& GuiColorDialog::GetCustomColors() + { + return customColors; + } + + bool GuiColorDialog::ShowDialog() + { + Array colors; + CopyFrom(colors, customColors); + colors.Resize(16); + + INativeDialogService::ColorDialogCustomColorOptions options = + !enabledCustomColor ? INativeDialogService::CustomColorDisabled : + !openedCustomColor ? INativeDialogService::CustomColorEnabled : + INativeDialogService::CustomColorOpened; + + auto service = GetCurrentController()->DialogService(); + if (!service->ShowColorDialog(GetHostWindow()->GetNativeWindow(), selectedColor, showSelection, options, &colors[0])) + { + return false; + } + + CopyFrom(customColors, colors); + SelectedColorChanged.Execute(GuiEventArgs()); + return true; + } + +/*********************************************************************** +GuiFontDialog +***********************************************************************/ + + GuiFontDialog::GuiFontDialog() + { + } + + GuiFontDialog::~GuiFontDialog() + { + } + + const FontProperties& GuiFontDialog::GetSelectedFont() + { + return selectedFont; + } + + void GuiFontDialog::SetSelectedFont(const FontProperties& value) + { + if (selectedFont != value) + { + selectedFont = value; + SelectedFontChanged.Execute(GuiEventArgs()); + } + } + + Color GuiFontDialog::GetSelectedColor() + { + return selectedColor; + } + + void GuiFontDialog::SetSelectedColor(Color value) + { + if (selectedColor != value) + { + selectedColor = value; + SelectedColorChanged.Execute(GuiEventArgs()); + } + } + + bool GuiFontDialog::GetShowSelection() + { + return showSelection; + } + + void GuiFontDialog::SetShowSelection(bool value) + { + showSelection = value; + } + + bool GuiFontDialog::GetShowEffect() + { + return showEffect; + } + + void GuiFontDialog::SetShowEffect(bool value) + { + showEffect = value; + } + + bool GuiFontDialog::GetForceFontExist() + { + return forceFontExist; + } + + void GuiFontDialog::SetForceFontExist(bool value) + { + forceFontExist = value; + } + + bool GuiFontDialog::ShowDialog() + { + auto service = GetCurrentController()->DialogService(); + if (!service->ShowFontDialog(GetHostWindow()->GetNativeWindow(), selectedFont, selectedColor, showSelection, showEffect, forceFontExist)) + { + return false; + } + + SelectedColorChanged.Execute(GuiEventArgs()); + SelectedFontChanged.Execute(GuiEventArgs()); + return true; + } + +/*********************************************************************** +GuiFileDialogBase +***********************************************************************/ + + GuiFileDialogBase::GuiFileDialogBase() + { + } + + GuiFileDialogBase::~GuiFileDialogBase() + { + } + + const WString& GuiFileDialogBase::GetFilter() + { + return filter; + } + + void GuiFileDialogBase::SetFilter(const WString& value) + { + filter = value; + } + + vint GuiFileDialogBase::GetFilterIndex() + { + return filterIndex; + } + + void GuiFileDialogBase::SetFilterIndex(vint value) + { + if (filterIndex != value) + { + filterIndex = value; + FilterIndexChanged.Execute(GuiEventArgs()); + } + } + + bool GuiFileDialogBase::GetEnabledPreview() + { + return enabledPreview; + } + + void GuiFileDialogBase::SetEnabledPreview(bool value) + { + enabledPreview = value; + } + + WString GuiFileDialogBase::GetTitle() + { + return title; + } + + void GuiFileDialogBase::SetTitle(const WString& value) + { + title = value; + } + + WString GuiFileDialogBase::GetFileName() + { + return fileName; + } + + void GuiFileDialogBase::SetFileName(const WString& value) + { + if (fileName != value) + { + FileNameChanged.Execute(GuiEventArgs()); + } + } + + WString GuiFileDialogBase::GetDirectory() + { + return directory; + } + + void GuiFileDialogBase::SetDirectory(const WString& value) + { + directory = value; + } + + WString GuiFileDialogBase::GetDefaultExtension() + { + return defaultExtension; + } + + void GuiFileDialogBase::SetDefaultExtension(const WString& value) + { + defaultExtension = value; + } + + INativeDialogService::FileDialogOptions GuiFileDialogBase::GetOptions() + { + return options; + } + + void GuiFileDialogBase::SetOptions(INativeDialogService::FileDialogOptions value) + { + options = value; + } + +/*********************************************************************** +GuiOpenFileDialog +***********************************************************************/ + + GuiOpenFileDialog::GuiOpenFileDialog() + { + } + + GuiOpenFileDialog::~GuiOpenFileDialog() + { + } + + collections::List& GuiOpenFileDialog::GetFileNames() + { + return fileNames; + } + + bool GuiOpenFileDialog::ShowDialog() + { + fileNames.Clear(); + auto service = GetCurrentController()->DialogService(); + if (!service->ShowFileDialog( + GetHostWindow()->GetNativeWindow(), + fileNames, + filterIndex, + (enabledPreview ? INativeDialogService::FileDialogOpenPreview : INativeDialogService::FileDialogOpen), + title, + fileName, + directory, + defaultExtension, + filter, + options)) + { + return false; + } + + if (fileNames.Count() > 0) + { + fileName = fileNames[0]; + FileNameChanged.Execute(GuiEventArgs()); + FilterIndexChanged.Execute(GuiEventArgs()); + } + return true; + } + +/*********************************************************************** +GuiSaveFileDialog +***********************************************************************/ + + GuiSaveFileDialog::GuiSaveFileDialog() + { + } + + GuiSaveFileDialog::~GuiSaveFileDialog() + { + } + + bool GuiSaveFileDialog::ShowDialog() + { + List fileNames; + auto service = GetCurrentController()->DialogService(); + if (!service->ShowFileDialog( + GetHostWindow()->GetNativeWindow(), + fileNames, + filterIndex, + (enabledPreview ? INativeDialogService::FileDialogSavePreview : INativeDialogService::FileDialogSave), + title, + fileName, + directory, + defaultExtension, + filter, + options)) + { + return false; + } + + if (fileNames.Count() > 0) + { + fileName = fileNames[0]; + FileNameChanged.Execute(GuiEventArgs()); + FilterIndexChanged.Execute(GuiEventArgs()); + } + return true; + } + } + } +} + +/*********************************************************************** +.\CONTROLS\GUILABELCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls { - public: - WString GetType()override + using namespace elements; + using namespace compositions; + using namespace collections; + using namespace reflection::description; + +/*********************************************************************** +GuiLabel +***********************************************************************/ + + void GuiLabel::BeforeControlTemplateUninstalled_() { - return L"Doc"; + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + textColorConsisted = (textColor == ct->GetDefaultTextColor()); } - bool XmlSerializable()override + void GuiLabel::AfterControlTemplateInstalled_(bool initialize) { - return true; + auto ct = GetControlTemplateObject(true); + if (initialize || textColorConsisted) + { + SetTextColor(ct->GetDefaultTextColor()); + } + else + { + ct->SetTextColor(textColor); + } } - bool StreamSerializable()override + GuiLabel::GuiLabel(theme::ThemeName themeName) + :GuiControl(themeName) { - return true; } - WString GetPreloadType()override + GuiLabel::~GuiLabel() { - return L"Xml"; } - bool IsDelayLoad()override + Color GuiLabel::GetTextColor() { - return true; + return textColor; } - IGuiResourceTypeResolver_IndirectLoad* IndirectLoad()override + void GuiLabel::SetTextColor(Color value) + { + if (textColor != value) + { + textColor = value; + GetControlTemplateObject(true)->SetTextColor(textColor); + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\GUISCROLLCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace compositions; + using namespace collections; + using namespace reflection::description; + +/*********************************************************************** +GuiScroll::CommandExecutor +***********************************************************************/ + + GuiScroll::CommandExecutor::CommandExecutor(GuiScroll* _scroll) + :scroll(_scroll) + { + } + + GuiScroll::CommandExecutor::~CommandExecutor() + { + } + + void GuiScroll::CommandExecutor::SmallDecrease() + { + scroll->SetPosition(scroll->GetPosition()-scroll->GetSmallMove()); + } + + void GuiScroll::CommandExecutor::SmallIncrease() + { + scroll->SetPosition(scroll->GetPosition()+scroll->GetSmallMove()); + } + + void GuiScroll::CommandExecutor::BigDecrease() + { + scroll->SetPosition(scroll->GetPosition()-scroll->GetBigMove()); + } + + void GuiScroll::CommandExecutor::BigIncrease() + { + scroll->SetPosition(scroll->GetPosition()+scroll->GetBigMove()); + } + + void GuiScroll::CommandExecutor::SetTotalSize(vint value) + { + scroll->SetTotalSize(value); + } + + void GuiScroll::CommandExecutor::SetPageSize(vint value) + { + scroll->SetPageSize(value); + } + + void GuiScroll::CommandExecutor::SetPosition(vint value) + { + scroll->SetPosition(value); + } + +/*********************************************************************** +GuiScroll +***********************************************************************/ + + void GuiScroll::BeforeControlTemplateUninstalled_() + { + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + ct->SetCommands(nullptr); + } + + void GuiScroll::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + ct->SetCommands(commandExecutor.Obj()); + ct->SetPageSize(pageSize); + ct->SetTotalSize(totalSize); + ct->SetPosition(position); + } + + GuiScroll::GuiScroll(theme::ThemeName themeName) + :GuiControl(themeName) + { + TotalSizeChanged.SetAssociatedComposition(boundsComposition); + PageSizeChanged.SetAssociatedComposition(boundsComposition); + PositionChanged.SetAssociatedComposition(boundsComposition); + SmallMoveChanged.SetAssociatedComposition(boundsComposition); + BigMoveChanged.SetAssociatedComposition(boundsComposition); + + commandExecutor = new CommandExecutor(this); + } + + GuiScroll::~GuiScroll() + { + } + + vint GuiScroll::GetTotalSize() + { + return totalSize; + } + + void GuiScroll::SetTotalSize(vint value) + { + if(totalSize!=value && 0totalSize) + { + SetPageSize(totalSize); + } + if(position>GetMaxPosition()) + { + SetPosition(GetMaxPosition()); + } + GetControlTemplateObject(true)->SetTotalSize(totalSize); + TotalSizeChanged.Execute(GetNotifyEventArguments()); + } + } + + vint GuiScroll::GetPageSize() + { + return pageSize; + } + + void GuiScroll::SetPageSize(vint value) + { + if(pageSize!=value && 0<=value && value<=totalSize) + { + pageSize=value; + if(position>GetMaxPosition()) + { + SetPosition(GetMaxPosition()); + } + GetControlTemplateObject(true)->SetPageSize(pageSize); + PageSizeChanged.Execute(GetNotifyEventArguments()); + } + } + + vint GuiScroll::GetPosition() + { + return position; + } + + void GuiScroll::SetPosition(vint value) + { + vint min=GetMinPosition(); + vint max=GetMaxPosition(); + vint newPosition= + valuemax?max: + value; + if(position!=newPosition) + { + position=newPosition; + GetControlTemplateObject(true)->SetPosition(position); + PositionChanged.Execute(GetNotifyEventArguments()); + } + } + + vint GuiScroll::GetSmallMove() + { + return smallMove; + } + + void GuiScroll::SetSmallMove(vint value) + { + if(value>0 && smallMove!=value) + { + smallMove=value; + SmallMoveChanged.Execute(GetNotifyEventArguments()); + } + } + + vint GuiScroll::GetBigMove() + { + return bigMove; + } + + void GuiScroll::SetBigMove(vint value) + { + if(value>0 && bigMove!=value) + { + bigMove=value; + BigMoveChanged.Execute(GetNotifyEventArguments()); + } + } + + vint GuiScroll::GetMinPosition() + { + return 0; + } + + vint GuiScroll::GetMaxPosition() + { + return totalSize-pageSize; + } + } + } +} + +/*********************************************************************** +.\CONTROLS\GUIWINDOWCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace compositions; + using namespace collections; + using namespace reflection::description; + +/*********************************************************************** +GuiControlHost +***********************************************************************/ + + void GuiControlHost::OnNativeWindowChanged() + { + } + + void GuiControlHost::OnVisualStatusChanged() + { + } + + controls::GuiControlHost* GuiControlHost::GetControlHostForInstance() { return this; } - Ptr Serialize(Ptr resource, Ptr content)override + GuiControl* GuiControlHost::GetTooltipOwner(Point location) { - if (auto obj = content.Cast()) + GuiGraphicsComposition* composition=this->GetBoundsComposition()->FindComposition(location, false); + if(composition) { - return obj->SaveToXml(); + GuiControl* control=composition->GetRelatedControl(); + while(control) + { + if(control->GetTooltipControl()) + { + return control; + } + control=control->GetParent(); + } } return nullptr; } - Ptr ResolveResource(Ptr resource, Ptr resolver, GuiResourceError::List& errors)override + void GuiControlHost::MoveIntoTooltipControl(GuiControl* tooltipControl, Point location) { - if(auto xml = resource->GetContent().Cast()) + if(tooltipLocation!=location) { - Ptr model = DocumentModel::LoadFromXml(resource, xml, resolver, errors); - return model; + tooltipLocation=location; + { + GuiControl* currentOwner=GetApplication()->GetTooltipOwner(); + if(currentOwner && currentOwner!=tooltipControl) + { + if(tooltipCloseDelay) + { + tooltipCloseDelay->Cancel(); + tooltipCloseDelay=0; + } + GetApplication()->DelayExecuteInMainThread([=]() + { + currentOwner->CloseTooltip(); + }, TooltipDelayCloseTime); + } + } + if(!tooltipControl) + { + if(tooltipOpenDelay) + { + tooltipOpenDelay->Cancel(); + tooltipOpenDelay=0; + } + } + else if(tooltipOpenDelay) + { + tooltipOpenDelay->Delay(TooltipDelayOpenTime); + } + else if(GetApplication()->GetTooltipOwner()!=tooltipControl) + { + tooltipOpenDelay=GetApplication()->DelayExecuteInMainThread([this]() + { + GuiControl* owner=GetTooltipOwner(tooltipLocation); + if(owner) + { + Point offset=owner->GetBoundsComposition()->GetGlobalBounds().LeftTop(); + Point p(tooltipLocation.x-offset.x, tooltipLocation.y-offset.y+24); + owner->DisplayTooltip(p); + tooltipOpenDelay=0; + + tooltipCloseDelay=GetApplication()->DelayExecuteInMainThread([this, owner]() + { + owner->CloseTooltip(); + }, TooltipDelayLifeTime); + } + }, TooltipDelayOpenTime); + } } - return nullptr; } - }; + + void GuiControlHost::MouseMoving(const NativeWindowMouseInfo& info) + { + if(!info.left && !info.middle && !info.right) + { + GuiControl* tooltipControl=GetTooltipOwner(tooltipLocation); + MoveIntoTooltipControl(tooltipControl, Point(info.x, info.y)); + } + } + + void GuiControlHost::MouseLeaved() + { + MoveIntoTooltipControl(0, Point(-1, -1)); + } + + void GuiControlHost::Moved() + { + OnVisualStatusChanged(); + } + + void GuiControlHost::Enabled() + { + GuiControl::SetEnabled(true); + OnVisualStatusChanged(); + } + + void GuiControlHost::Disabled() + { + GuiControl::SetEnabled(false); + OnVisualStatusChanged(); + } + + void GuiControlHost::GotFocus() + { + WindowGotFocus.Execute(GetNotifyEventArguments()); + OnVisualStatusChanged(); + } + + void GuiControlHost::LostFocus() + { + WindowLostFocus.Execute(GetNotifyEventArguments()); + OnVisualStatusChanged(); + } + + void GuiControlHost::Activated() + { + WindowActivated.Execute(GetNotifyEventArguments()); + OnVisualStatusChanged(); + } + + void GuiControlHost::Deactivated() + { + WindowDeactivated.Execute(GetNotifyEventArguments()); + OnVisualStatusChanged(); + } + + void GuiControlHost::Opened() + { + WindowOpened.Execute(GetNotifyEventArguments()); + } + + void GuiControlHost::Closing(bool& cancel) + { + GuiRequestEventArgs arguments(boundsComposition); + arguments.cancel=cancel; + WindowClosing.Execute(arguments); + if(!arguments.handled) + { + cancel=arguments.cancel; + } + } + + void GuiControlHost::Closed() + { + WindowClosed.Execute(GetNotifyEventArguments()); + } + + void GuiControlHost::Destroying() + { + WindowDestroying.Execute(GetNotifyEventArguments()); + calledDestroyed = true; + if (deleteWhenDestroyed) + { + GetApplication()->InvokeInMainThread(this, [=]() + { + delete this; + }); + } + SetNativeWindow(nullptr); + } + + void GuiControlHost::UpdateClientSizeAfterRendering(Size clientSize) + { + SetClientSize(clientSize); + } + + GuiControlHost::GuiControlHost(theme::ThemeName themeName) + :GuiControl(themeName) + { + boundsComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + + WindowGotFocus.SetAssociatedComposition(boundsComposition); + WindowLostFocus.SetAssociatedComposition(boundsComposition); + WindowActivated.SetAssociatedComposition(boundsComposition); + WindowDeactivated.SetAssociatedComposition(boundsComposition); + WindowOpened.SetAssociatedComposition(boundsComposition); + WindowClosing.SetAssociatedComposition(boundsComposition); + WindowClosed.SetAssociatedComposition(boundsComposition); + WindowDestroying.SetAssociatedComposition(boundsComposition); + + host=new GuiGraphicsHost(this, boundsComposition); + sharedPtrDestructorProc = 0; + } + + GuiControlHost::~GuiControlHost() + { + FinalizeInstanceRecursively(this); + OnBeforeReleaseGraphicsHost(); + delete host; + } + + void GuiControlHost::DeleteAfterProcessingAllEvents() + { + auto window = host->GetNativeWindow(); + if (calledDestroyed || !window) + { + delete this; + } + else + { + deleteWhenDestroyed = true; + GetCurrentController()->WindowService()->DestroyNativeWindow(window); + } + } + + compositions::GuiGraphicsHost* GuiControlHost::GetGraphicsHost() + { + return host; + } + + compositions::GuiGraphicsComposition* GuiControlHost::GetMainComposition() + { + return host->GetMainComposition(); + } + + INativeWindow* GuiControlHost::GetNativeWindow() + { + return host->GetNativeWindow(); + } + + void GuiControlHost::SetNativeWindow(INativeWindow* window) + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->UninstallListener(this); + } + host->SetNativeWindow(window); + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->InstallListener(this); + } + OnNativeWindowChanged(); + } + + void GuiControlHost::ForceCalculateSizeImmediately() + { + auto bounds = GetBounds(); + boundsComposition->ForceCalculateSizeImmediately(); + SetBounds(bounds); + } + + bool GuiControlHost::GetEnabled() + { + if(host->GetNativeWindow()) + { + return host->GetNativeWindow()->IsEnabled(); + } + else + { + return false; + } + } + + void GuiControlHost::SetEnabled(bool value) + { + if(host->GetNativeWindow()) + { + if(value) + { + host->GetNativeWindow()->Enable(); + } + else + { + host->GetNativeWindow()->Disable(); + } + } + } + + bool GuiControlHost::GetFocused() + { + if(host->GetNativeWindow()) + { + return host->GetNativeWindow()->IsFocused(); + } + else + { + return false; + } + } + + void GuiControlHost::SetFocused() + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->SetFocus(); + } + } + + bool GuiControlHost::GetActivated() + { + if(host->GetNativeWindow()) + { + return host->GetNativeWindow()->IsActivated(); + } + else + { + return false; + } + } + + void GuiControlHost::SetActivated() + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->SetActivate(); + } + } + + bool GuiControlHost::GetShowInTaskBar() + { + if(host->GetNativeWindow()) + { + return host->GetNativeWindow()->IsAppearedInTaskBar(); + } + else + { + return false; + } + } + + void GuiControlHost::SetShowInTaskBar(bool value) + { + if(host->GetNativeWindow()) + { + if(value) + { + host->GetNativeWindow()->ShowInTaskBar(); + } + else + { + host->GetNativeWindow()->HideInTaskBar(); + } + } + } + + bool GuiControlHost::GetEnabledActivate() + { + if(host->GetNativeWindow()) + { + return host->GetNativeWindow()->IsEnabledActivate(); + } + else + { + return false; + } + } + + void GuiControlHost::SetEnabledActivate(bool value) + { + if(host->GetNativeWindow()) + { + if(value) + { + host->GetNativeWindow()->EnableActivate(); + } + else + { + host->GetNativeWindow()->DisableActivate(); + } + } + } + + bool GuiControlHost::GetTopMost() + { + if(host->GetNativeWindow()) + { + return host->GetNativeWindow()->GetTopMost(); + } + else + { + return false; + } + } + + void GuiControlHost::SetTopMost(bool topmost) + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->SetTopMost(topmost); + } + } + + compositions::IGuiShortcutKeyManager* GuiControlHost::GetShortcutKeyManager() + { + return host->GetShortcutKeyManager(); + } + + void GuiControlHost::SetShortcutKeyManager(compositions::IGuiShortcutKeyManager* value) + { + host->SetShortcutKeyManager(value); + } + + compositions::GuiGraphicsTimerManager* GuiControlHost::GetTimerManager() + { + return host->GetTimerManager(); + } + + Size GuiControlHost::GetClientSize() + { + if(host->GetNativeWindow()) + { + return host->GetNativeWindow()->GetClientSize(); + } + else + { + return Size(0, 0); + } + } + + void GuiControlHost::SetClientSize(Size value) + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->SetClientSize(value); + } + } + + Rect GuiControlHost::GetBounds() + { + if(host->GetNativeWindow()) + { + return host->GetNativeWindow()->GetBounds(); + } + else + { + return Rect(); + } + } + + void GuiControlHost::SetBounds(Rect value) + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->SetBounds(value); + } + } + + GuiControlHost* GuiControlHost::GetRelatedControlHost() + { + return this; + } + + const WString& GuiControlHost::GetText() + { + WString result; + if(host->GetNativeWindow()) + { + result=host->GetNativeWindow()->GetTitle(); + } + if(result!=GuiControl::GetText()) + { + GuiControl::SetText(result); + } + return GuiControl::GetText(); + } + + void GuiControlHost::SetText(const WString& value) + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->SetTitle(value); + GuiControl::SetText(value); + } + } + + INativeScreen* GuiControlHost::GetRelatedScreen() + { + if(host->GetNativeWindow()) + { + return GetCurrentController()->ScreenService()->GetScreen(host->GetNativeWindow()); + } + else + { + return 0; + } + } + + void GuiControlHost::Show() + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->Show(); + } + } + + void GuiControlHost::ShowDeactivated() + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->ShowDeactivated(); + } + } + + void GuiControlHost::ShowRestored() + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->ShowRestored(); + } + } + + void GuiControlHost::ShowMaximized() + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->ShowMaximized(); + } + } + + void GuiControlHost::ShowMinimized() + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->ShowMinimized(); + } + } + + void GuiControlHost::Hide() + { + if(host->GetNativeWindow()) + { + host->GetNativeWindow()->Hide(false); + } + } + + void GuiControlHost::Close() + { + if (auto window = host->GetNativeWindow()) + { + auto mainWindow = GetCurrentController()->WindowService()->GetMainWindow(); + if (mainWindow == window) + { + SetNativeWindow(nullptr); + GetCurrentController()->WindowService()->DestroyNativeWindow(window); + } + else + { + window->Hide(false); + } + } + } + + bool GuiControlHost::GetOpening() + { + INativeWindow* window=host->GetNativeWindow(); + if(window) + { + return window->IsVisible(); + } + return false; + } /*********************************************************************** -Type Resolver Plugin +GuiWindow ***********************************************************************/ - class GuiResourceTypeResolversPlugin : public Object, public IGuiPlugin - { - public: - - GUI_PLUGIN_NAME(GacUI_Res_TypeResolvers) - { - GUI_PLUGIN_DEPEND(GacUI_Res_ResourceResolver); - } - - void Load()override - { - IGuiResourceResolverManager* manager=GetResourceResolverManager(); - manager->SetTypeResolver(new GuiResourceImageTypeResolver); - manager->SetTypeResolver(new GuiResourceTextTypeResolver); - manager->SetTypeResolver(new GuiResourceXmlTypeResolver); - manager->SetTypeResolver(new GuiResourceDocTypeResolver); - } - - void Unload()override + void GuiWindow::BeforeControlTemplateUninstalled_() { } - }; - GUI_REGISTER_PLUGIN(GuiResourceTypeResolversPlugin) + + void GuiWindow::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); +#define FIX_WINDOW_PROPERTY(VARIABLE, NAME) \ + switch (ct->Get ## NAME ## Option()) \ + { \ + case templates::BoolOption::AlwaysTrue: \ + VARIABLE = true; \ + break; \ + case templates::BoolOption::AlwaysFalse: \ + VARIABLE = false; \ + break; \ + default:; \ + } \ + + FIX_WINDOW_PROPERTY(hasMaximizedBox, MaximizedBox) + FIX_WINDOW_PROPERTY(hasMinimizedBox, MinimizedBox) + FIX_WINDOW_PROPERTY(hasBorder, Border) + FIX_WINDOW_PROPERTY(hasSizeBox, SizeBox) + FIX_WINDOW_PROPERTY(isIconVisible, IconVisible) + FIX_WINDOW_PROPERTY(hasTitleBar, TitleBar) + +#undef FIX_WINDOW_PROPERTY + ct->SetMaximizedBox(hasMaximizedBox); + ct->SetMinimizedBox(hasMinimizedBox); + ct->SetBorder(hasBorder); + ct->SetSizeBox(hasSizeBox); + ct->SetIconVisible(isIconVisible); + ct->SetTitleBar(hasTitleBar); + ct->SetMaximized(GetNativeWindow()->GetSizeState() != INativeWindow::Maximized); + SyncNativeWindowProperties(); + } + + void GuiWindow::SyncNativeWindowProperties() + { + if (auto window = GetNativeWindow()) + { + if (GetControlTemplateObject(true)->GetCustomFrameEnabled()) + { + window->EnableCustomFrameMode(); + window->SetBorder(false); + } + else + { + window->DisableCustomFrameMode(); + window->SetBorder(hasBorder); + } + + window->SetMaximizedBox(hasMaximizedBox); + window->SetMinimizedBox(hasMinimizedBox); + window->SetSizeBox(hasSizeBox); + window->SetIconVisible(isIconVisible); + window->SetTitleBar(hasTitleBar); + } + } + + void GuiWindow::Moved() + { + GuiControlHost::Moved(); + GetControlTemplateObject(true)->SetMaximized(GetNativeWindow()->GetSizeState() != INativeWindow::Maximized); + } + + void GuiWindow::OnNativeWindowChanged() + { + SyncNativeWindowProperties(); + GuiControlHost::OnNativeWindowChanged(); + } + + void GuiWindow::OnVisualStatusChanged() + { + GuiControlHost::OnVisualStatusChanged(); + } + + void GuiWindow::MouseClickedOnOtherWindow(GuiWindow* window) + { + } + + compositions::GuiGraphicsComposition* GuiWindow::GetAltComposition() + { + return boundsComposition; + } + + compositions::IGuiAltActionHost* GuiWindow::GetPreviousAltHost() + { + return previousAltHost; + } + + void GuiWindow::OnActivatedAltHost(IGuiAltActionHost* previousHost) + { + previousAltHost = previousHost; + } + + void GuiWindow::OnDeactivatedAltHost() + { + previousAltHost = 0; + } + + void GuiWindow::CollectAltActions(collections::Group& actions) + { + IGuiAltActionHost::CollectAltActionsFromControl(this, actions); + } + + GuiWindow::GuiWindow(theme::ThemeName themeName) + :GuiControlHost(themeName) + ,previousAltHost(0) + { + INativeWindow* window=GetCurrentController()->WindowService()->CreateNativeWindow(); + SetNativeWindow(window); + GetApplication()->RegisterWindow(this); + ClipboardUpdated.SetAssociatedComposition(boundsComposition); + } + + GuiWindow::~GuiWindow() + { + FinalizeAggregation(); + GetApplication()->UnregisterWindow(this); + INativeWindow* window=host->GetNativeWindow(); + if(window) + { + SetNativeWindow(nullptr); + GetCurrentController()->WindowService()->DestroyNativeWindow(window); + } + } + + IDescriptable* GuiWindow::QueryService(const WString& identifier) + { + if (identifier == IGuiAltActionHost::Identifier) + { + return (IGuiAltActionHost*)this; + } + else + { + return GuiControlHost::QueryService(identifier); + } + } + + void GuiWindow::MoveToScreenCenter() + { + MoveToScreenCenter(GetRelatedScreen()); + } + + void GuiWindow::MoveToScreenCenter(INativeScreen* screen) + { + if (screen) + { + Rect screenBounds = screen->GetClientBounds(); + Rect windowBounds = GetBounds(); + SetBounds( + Rect( + Point( + screenBounds.Left() + (screenBounds.Width() - windowBounds.Width()) / 2, + screenBounds.Top() + (screenBounds.Height() - windowBounds.Height()) / 2 + ), + windowBounds.GetSize() + ) + ); + } + } + +#define IMPL_WINDOW_PROPERTY(VARIABLE, NAME, CONDITION_BREAK) \ + bool GuiWindow::Get ## NAME() \ + { \ + return VARIABLE; \ + } \ + void GuiWindow::Set ## NAME(bool visible) \ + { \ + auto ct = GetControlTemplateObject(true); \ + if (ct->Get ## NAME ## Option() == templates::BoolOption::Customizable) \ + { \ + VARIABLE = visible; \ + ct->Set ## NAME(visible); \ + if (auto window = GetNativeWindow()) \ + { \ + CONDITION_BREAK \ + window->Set ## NAME(visible); \ + } \ + } \ + } \ + +#define IMPL_WINDOW_PROPERTY_EMPTY_CONDITION +#define IMPL_WINDOW_PROPERTY_BORDER_CONDITION if (ct->GetCustomFrameEnabled()) return; + + IMPL_WINDOW_PROPERTY(hasMaximizedBox, MaximizedBox, IMPL_WINDOW_PROPERTY_EMPTY_CONDITION) + IMPL_WINDOW_PROPERTY(hasMinimizedBox, MinimizedBox, IMPL_WINDOW_PROPERTY_EMPTY_CONDITION) + IMPL_WINDOW_PROPERTY(hasBorder, Border, IMPL_WINDOW_PROPERTY_BORDER_CONDITION) + IMPL_WINDOW_PROPERTY(hasSizeBox, SizeBox, IMPL_WINDOW_PROPERTY_EMPTY_CONDITION) + IMPL_WINDOW_PROPERTY(isIconVisible, IconVisible, IMPL_WINDOW_PROPERTY_EMPTY_CONDITION) + IMPL_WINDOW_PROPERTY(hasTitleBar, TitleBar, IMPL_WINDOW_PROPERTY_EMPTY_CONDITION) + +#undef IMPL_WINDOW_PROPERTY_BORDER_CONDITION +#undef IMPL_WINDOW_PROPERTY_EMPTY_CONDITION +#undef IMPL_WINDOW_PROPERTY + + void GuiWindow::ShowModal(GuiWindow* owner, const Func& callback) + { + owner->SetEnabled(false); + GetNativeWindow()->SetParent(owner->GetNativeWindow()); + auto container = MakePtr(); + container->handler = WindowClosed.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) + { + GetApplication()->InvokeInMainThread(this, [=]() + { + WindowClosed.Detach(container->handler); + container->handler = nullptr; + GetNativeWindow()->SetParent(0); + callback(); + owner->SetEnabled(true); + owner->SetActivated(); + }); + }); + Show(); + } + + void GuiWindow::ShowModalAndDelete(GuiWindow* owner, const Func& callback) + { + ShowModal(owner, [=]() + { + callback(); + DeleteAfterProcessingAllEvents(); + }); + } + + Ptr GuiWindow::ShowModalAsync(GuiWindow* owner) + { + auto future = IFuture::Create(); + ShowModal(owner, [promise = future->GetPromise()]() + { + promise->SendResult({}); + }); + return future; + } + +/*********************************************************************** +GuiPopup +***********************************************************************/ + + void GuiPopup::UpdateClientSizeAfterRendering(Size clientSize) + { + if (popupType == -1) + { + GuiWindow::UpdateClientSizeAfterRendering(clientSize); + } + else + { + auto window = GetNativeWindow(); + auto position = CalculatePopupPosition(clientSize, popupType, popupInfo); + window->SetBounds(Rect(position, clientSize)); + } + } + + void GuiPopup::MouseClickedOnOtherWindow(GuiWindow* window) + { + Hide(); + } + + void GuiPopup::PopupOpened(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + GetApplication()->RegisterPopupOpened(this); + } + + void GuiPopup::PopupClosed(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + popupType = -1; + GetApplication()->RegisterPopupClosed(this); + if(auto window = GetNativeWindow()) + { + window->SetParent(nullptr); + } + } + + Point GuiPopup::CalculatePopupPosition(Size size, Point location, INativeScreen* screen) + { + Rect screenBounds = screen->GetClientBounds(); + + if (location.x < screenBounds.x1) + { + location.x = screenBounds.x1; + } + else if (location.x + size.x > screenBounds.x2) + { + location.x = screenBounds.x2 - size.x; + } + + if (location.y < screenBounds.y1) + { + location.y = screenBounds.y1; + } + else if (location.y + size.y > screenBounds.y2) + { + location.y = screenBounds.y2 - size.y; + } + + return location; + } + + bool GuiPopup::IsClippedByScreen(Size size, Point location, INativeScreen* screen) + { + Rect screenBounds = screen->GetClientBounds(); + Rect windowBounds(location, size); + return !screenBounds.Contains(windowBounds.LeftTop()) || !screenBounds.Contains(windowBounds.RightBottom()); + } + + Point GuiPopup::CalculatePopupPosition(Size size, GuiControl* control, INativeWindow* controlWindow, Rect bounds, bool preferredTopBottomSide) + { + Point controlClientOffset = control->GetBoundsComposition()->GetGlobalBounds().LeftTop(); + Point controlWindowOffset = controlWindow->GetClientBoundsInScreen().LeftTop(); + bounds.x1 += controlClientOffset.x + controlWindowOffset.x; + bounds.x2 += controlClientOffset.x + controlWindowOffset.x; + bounds.y1 += controlClientOffset.y + controlWindowOffset.y; + bounds.y2 += controlClientOffset.y + controlWindowOffset.y; + + Point locations[4]; + if (preferredTopBottomSide) + { + locations[0] = Point(bounds.x1, bounds.y2); + locations[1] = Point(bounds.x2 - size.x, bounds.y2); + locations[2] = Point(bounds.x1, bounds.y1 - size.y); + locations[3] = Point(bounds.x2 - size.x, bounds.y1 - size.y); + } + else + { + locations[0] = Point(bounds.x2, bounds.y1); + locations[1] = Point(bounds.x2, bounds.y2 - size.y); + locations[2] = Point(bounds.x1 - size.x, bounds.y1); + locations[3] = Point(bounds.x1 - size.x, bounds.y2 - size.y); + } + + auto screen = GetCurrentController()->ScreenService()->GetScreen(controlWindow); + for (vint i = 0; i < 4; i++) + { + if (!IsClippedByScreen(size, locations[i], screen)) + { + return CalculatePopupPosition(size, locations[i], screen); + } + } + return CalculatePopupPosition(size, locations[0], screen); + } + + Point GuiPopup::CalculatePopupPosition(Size size, GuiControl* control, INativeWindow* controlWindow, Point location) + { + Point locations[4]; + Rect controlBounds = control->GetBoundsComposition()->GetGlobalBounds(); + + Point controlClientOffset = controlWindow->GetClientBoundsInScreen().LeftTop(); + vint x = controlBounds.x1 + controlClientOffset.x + location.x; + vint y = controlBounds.y1 + controlClientOffset.y + location.y; + return CalculatePopupPosition(size, Point(x, y), GetCurrentController()->ScreenService()->GetScreen(controlWindow)); + } + + Point GuiPopup::CalculatePopupPosition(Size size, GuiControl* control, INativeWindow* controlWindow, bool preferredTopBottomSide) + { + Rect bounds(Point(0, 0), control->GetBoundsComposition()->GetBounds().GetSize()); + return CalculatePopupPosition(size, control, controlWindow, bounds, preferredTopBottomSide); + } + + Point GuiPopup::CalculatePopupPosition(Size size, vint popupType, const PopupInfo& popupInfo) + { + switch (popupType) + { + case 1: + return CalculatePopupPosition(size, popupInfo._1.location, popupInfo._1.screen); + case 2: + return CalculatePopupPosition(size, popupInfo._2.control, popupInfo._2.controlWindow, popupInfo._2.bounds, popupInfo._2.preferredTopBottomSide); + case 3: + return CalculatePopupPosition(size, popupInfo._3.control, popupInfo._3.controlWindow, popupInfo._3.location); + case 4: + return CalculatePopupPosition(size, popupInfo._4.control, popupInfo._4.controlWindow, popupInfo._4.preferredTopBottomSide); + default: + CHECK_FAIL(L"vl::presentation::controls::GuiPopup::CalculatePopupPosition(Size, const PopupInfo&)#Internal error."); + } + } + + void GuiPopup::ShowPopupInternal() + { + auto window = GetNativeWindow(); + UpdateClientSizeAfterRendering(window->GetBounds().GetSize()); + switch (popupType) + { + case 2: window->SetParent(popupInfo._2.controlWindow); break; + case 3: window->SetParent(popupInfo._3.controlWindow); break; + case 4: window->SetParent(popupInfo._4.controlWindow); break; + } + ShowDeactivated(); + } + + GuiPopup::GuiPopup(theme::ThemeName themeName) + :GuiWindow(themeName) + { + SetMinimizedBox(false); + SetMaximizedBox(false); + SetSizeBox(false); + SetTitleBar(false); + SetShowInTaskBar(false); + + WindowOpened.AttachMethod(this, &GuiPopup::PopupOpened); + WindowClosed.AttachMethod(this, &GuiPopup::PopupClosed); + } + + GuiPopup::~GuiPopup() + { + GetApplication()->RegisterPopupClosed(this); + } + + void GuiPopup::ShowPopup(Point location, INativeScreen* screen) + { + if (auto window = GetNativeWindow()) + { + if (!screen) + { + SetBounds(Rect(location, GetBounds().GetSize())); + screen = GetCurrentController()->ScreenService()->GetScreen(window); + } + + popupType = 1; + popupInfo._1.location = location; + popupInfo._1.screen = screen; + ShowPopupInternal(); + } + } + + void GuiPopup::ShowPopup(GuiControl* control, Rect bounds, bool preferredTopBottomSide) + { + if (auto window = GetNativeWindow()) + { + if (auto controlHost = control->GetBoundsComposition()->GetRelatedControlHost()) + { + if (auto controlWindow = controlHost->GetNativeWindow()) + { + popupType = 2; + popupInfo._2.control = control; + popupInfo._2.controlWindow = controlWindow; + popupInfo._2.bounds = bounds; + popupInfo._2.preferredTopBottomSide = preferredTopBottomSide; + ShowPopupInternal(); + } + } + } + } + + void GuiPopup::ShowPopup(GuiControl* control, Point location) + { + if (auto window = GetNativeWindow()) + { + if (auto controlHost = control->GetBoundsComposition()->GetRelatedControlHost()) + { + if (auto controlWindow = controlHost->GetNativeWindow()) + { + popupType = 3; + popupInfo._3.control = control; + popupInfo._3.controlWindow = controlWindow; + popupInfo._3.location = location; + ShowPopupInternal(); + } + } + } + } + + void GuiPopup::ShowPopup(GuiControl* control, bool preferredTopBottomSide) + { + if (auto window = GetNativeWindow()) + { + if (auto controlHost = control->GetBoundsComposition()->GetRelatedControlHost()) + { + if (auto controlWindow = controlHost->GetNativeWindow()) + { + popupType = 4; + popupInfo._4.control = control; + popupInfo._4.controlWindow = controlWindow; + popupInfo._4.preferredTopBottomSide = preferredTopBottomSide; + ShowPopupInternal(); + } + } + } + } + +/*********************************************************************** +GuiPopup +***********************************************************************/ + + void GuiTooltip::GlobalTimer() + { + SetClientSize(GetClientSize()); + } + + void GuiTooltip::TooltipOpened(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + } + + void GuiTooltip::TooltipClosed(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + SetTemporaryContentControl(0); + } + + GuiTooltip::GuiTooltip(theme::ThemeName themeName) + :GuiPopup(themeName) + ,temporaryContentControl(0) + { + containerComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + containerComposition->SetPreferredMinSize(Size(20, 10)); + GetCurrentController()->CallbackService()->InstallListener(this); + + WindowOpened.AttachMethod(this, &GuiTooltip::TooltipOpened); + WindowClosed.AttachMethod(this, &GuiTooltip::TooltipClosed); + } + + GuiTooltip::~GuiTooltip() + { + GetCurrentController()->CallbackService()->UninstallListener(this); + } + + vint GuiTooltip::GetPreferredContentWidth() + { + return containerComposition->GetPreferredMinSize().x; + } + + void GuiTooltip::SetPreferredContentWidth(vint value) + { + containerComposition->SetPreferredMinSize(Size(value, 10)); + } + + GuiControl* GuiTooltip::GetTemporaryContentControl() + { + return temporaryContentControl; + } + + void GuiTooltip::SetTemporaryContentControl(GuiControl* control) + { + if(temporaryContentControl && HasChild(temporaryContentControl)) + { + containerComposition->RemoveChild(temporaryContentControl->GetBoundsComposition()); + temporaryContentControl=0; + } + temporaryContentControl=control; + if(control) + { + control->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + AddChild(control); + } + } + } } } /*********************************************************************** -.\RESOURCES\GUIRESOURCEMANAGER.CPP +.\CONTROLS\LISTCONTROLPACKAGE\GUIBINDABLEDATAGRID.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace description; + using namespace templates; + + namespace list + { + +/*********************************************************************** +DataFilterBase +***********************************************************************/ + + void DataFilterBase::InvokeOnProcessorChanged() + { + if (callback) + { + callback->OnProcessorChanged(); + } + } + + DataFilterBase::DataFilterBase() + { + } + + void DataFilterBase::SetCallback(IDataProcessorCallback* value) + { + callback = value; + } + +/*********************************************************************** +DataMultipleFilter +***********************************************************************/ + + DataMultipleFilter::DataMultipleFilter() + { + } + + bool DataMultipleFilter::AddSubFilter(Ptr value) + { + if (!value) return false; + if (filters.Contains(value.Obj())) return false; + filters.Add(value); + value->SetCallback(callback); + InvokeOnProcessorChanged(); + return true; + } + + bool DataMultipleFilter::RemoveSubFilter(Ptr value) + { + if (!value) return false; + if (!filters.Contains(value.Obj())) return false; + value->SetCallback(nullptr); + filters.Remove(value.Obj()); + InvokeOnProcessorChanged(); + return true; + } + + void DataMultipleFilter::SetCallback(IDataProcessorCallback* value) + { + DataFilterBase::SetCallback(value); + for (vint i = 0; i < filters.Count(); i++) + { + filters[i]->SetCallback(value); + } + } + +/*********************************************************************** +DataAndFilter +***********************************************************************/ + + DataAndFilter::DataAndFilter() + { + } + + bool DataAndFilter::Filter(const description::Value& row) + { + return From(filters) + .All([row](Ptr filter) + { + return filter->Filter(row); + }); + } + +/*********************************************************************** +DataOrFilter +***********************************************************************/ + + DataOrFilter::DataOrFilter() + { + } + + bool DataOrFilter::Filter(const description::Value& row) + { + return From(filters) + .Any([row](Ptr filter) + { + return filter->Filter(row); + }); + } + +/*********************************************************************** +DataNotFilter +***********************************************************************/ + + DataNotFilter::DataNotFilter() + { + } + + bool DataNotFilter::SetSubFilter(Ptr value) + { + if (filter == value) return false; + if (filter) filter->SetCallback(nullptr); + filter = value; + if (filter) filter->SetCallback(callback); + InvokeOnProcessorChanged(); + return true; + } + + void DataNotFilter::SetCallback(IDataProcessorCallback* value) + { + DataFilterBase::SetCallback(value); + if (filter) filter->SetCallback(value); + } + + bool DataNotFilter::Filter(const description::Value& row) + { + return filter ? true : !filter->Filter(row); + } + +/*********************************************************************** +DataSorterBase +***********************************************************************/ + + void DataSorterBase::InvokeOnProcessorChanged() + { + if (callback) + { + callback->OnProcessorChanged(); + } + } + + DataSorterBase::DataSorterBase() + { + } + + void DataSorterBase::SetCallback(IDataProcessorCallback* value) + { + callback = value; + } + +/*********************************************************************** +DataMultipleSorter +***********************************************************************/ + + DataMultipleSorter::DataMultipleSorter() + { + } + + bool DataMultipleSorter::SetLeftSorter(Ptr value) + { + if (leftSorter == value) return false; + if (leftSorter) leftSorter->SetCallback(nullptr); + leftSorter = value; + if (leftSorter) leftSorter->SetCallback(callback); + return true; + } + + bool DataMultipleSorter::SetRightSorter(Ptr value) + { + if (rightSorter == value) return false; + if (rightSorter) rightSorter->SetCallback(nullptr); + rightSorter = value; + if (rightSorter) rightSorter->SetCallback(callback); + return true; + } + + void DataMultipleSorter::SetCallback(IDataProcessorCallback* value) + { + DataSorterBase::SetCallback(value); + if (leftSorter) leftSorter->SetCallback(value); + if (rightSorter) rightSorter->SetCallback(value); + } + + vint DataMultipleSorter::Compare(const description::Value& row1, const description::Value& row2) + { + if (leftSorter) + { + vint result = leftSorter->Compare(row1, row2); + if (result != 0) return result; + } + if (rightSorter) + { + vint result = rightSorter->Compare(row1, row2); + if (result != 0) return result; + } + return 0; + } + +/*********************************************************************** +DataReverseSorter +***********************************************************************/ + + DataReverseSorter::DataReverseSorter() + { + } + + bool DataReverseSorter::SetSubSorter(Ptr value) + { + if (sorter == value) return false; + if (sorter) sorter->SetCallback(nullptr); + sorter = value; + if (sorter) sorter->SetCallback(callback); + return true; + } + + void DataReverseSorter::SetCallback(IDataProcessorCallback* value) + { + DataSorterBase::SetCallback(value); + if (sorter) sorter->SetCallback(value); + } + + vint DataReverseSorter::Compare(const description::Value& row1, const description::Value& row2) + { + return sorter ? -sorter->Compare(row1, row2) : 0; + } + +/*********************************************************************** +DataColumn +***********************************************************************/ + + void DataColumn::NotifyAllColumnsUpdate(bool affectItem) + { + if (dataProvider) + { + vint index = dataProvider->columns.IndexOf(this); + if (index != -1) + { + dataProvider->columns.NotifyColumnUpdated(index, affectItem); + } + } + } + + DataColumn::DataColumn() + { + } + + DataColumn::~DataColumn() + { + if (popup && ownPopup) + { + SafeDeleteControl(popup); + } + } + + WString DataColumn::GetText() + { + return text; + } + + void DataColumn::SetText(const WString& value) + { + if (text != value) + { + text = value; + NotifyAllColumnsUpdate(false); + } + } + + vint DataColumn::GetSize() + { + return size; + } + + void DataColumn::SetSize(vint value) + { + if (size != value) + { + size = value; + NotifyAllColumnsUpdate(false); + } + } + + bool DataColumn::GetOwnPopup() + { + return ownPopup; + } + + void DataColumn::SetOwnPopup(bool value) + { + ownPopup = value; + } + + GuiMenu* DataColumn::GetPopup() + { + return popup; + } + + void DataColumn::SetPopup(GuiMenu* value) + { + if (popup != value) + { + popup = value; + NotifyAllColumnsUpdate(false); + } + } + + Ptr DataColumn::GetFilter() + { + return associatedFilter; + } + + void DataColumn::SetFilter(Ptr value) + { + if (associatedFilter) associatedFilter->SetCallback(nullptr); + associatedFilter = value; + if (associatedFilter) associatedFilter->SetCallback(dataProvider); + NotifyAllColumnsUpdate(false); + } + + Ptr DataColumn::GetSorter() + { + return associatedSorter; + } + + void DataColumn::SetSorter(Ptr value) + { + if (associatedSorter) associatedSorter->SetCallback(nullptr); + associatedSorter = value; + if (associatedSorter) associatedSorter->SetCallback(dataProvider); + NotifyAllColumnsUpdate(false); + } + + Ptr DataColumn::GetVisualizerFactory() + { + return visualizerFactory; + } + + void DataColumn::SetVisualizerFactory(Ptr value) + { + visualizerFactory = value; + NotifyAllColumnsUpdate(true); + } + + Ptr DataColumn::GetEditorFactory() + { + return editorFactory; + } + + void DataColumn::SetEditorFactory(Ptr value) + { + editorFactory = value; + NotifyAllColumnsUpdate(true); + } + + WString DataColumn::GetCellText(vint row) + { + if (0 <= row && row < dataProvider->Count()) + { + return ReadProperty(dataProvider->GetBindingValue(row), textProperty); + } + return L""; + } + + description::Value DataColumn::GetCellValue(vint row) + { + if (0 <= row && row < dataProvider->Count()) + { + return ReadProperty(dataProvider->GetBindingValue(row), valueProperty); + } + return Value(); + } + + void DataColumn::SetCellValue(vint row, description::Value value) + { + if (0 <= row && row < dataProvider->Count()) + { + auto rowValue = dataProvider->GetBindingValue(row); + WriteProperty(rowValue, valueProperty, value); + dataProvider->InvokeOnItemModified(row, 1, 1); + } + } + + ItemProperty DataColumn::GetTextProperty() + { + return textProperty; + } + + void DataColumn::SetTextProperty(const ItemProperty& value) + { + if (textProperty != value) + { + textProperty = value; + NotifyAllColumnsUpdate(true); + compositions::GuiEventArgs arguments; + TextPropertyChanged.Execute(arguments); + } + } + + WritableItemProperty DataColumn::GetValueProperty() + { + return valueProperty; + } + + void DataColumn::SetValueProperty(const WritableItemProperty& value) + { + if (valueProperty != value) + { + valueProperty = value; + NotifyAllColumnsUpdate(true); + compositions::GuiEventArgs arguments; + ValuePropertyChanged.Execute(arguments); + } + } + +/*********************************************************************** +DataColumns +***********************************************************************/ + + void DataColumns::NotifyColumnUpdated(vint index, bool affectItem) + { + affectItemFlag = affectItem; + NotifyUpdateInternal(index, 1, 1); + affectItemFlag = true; + } + + void DataColumns::NotifyUpdateInternal(vint start, vint count, vint newCount) + { + dataProvider->NotifyAllColumnsUpdate(); + if (affectItemFlag) + { + dataProvider->NotifyAllItemsUpdate(); + } + } + + bool DataColumns::QueryInsert(vint index, const Ptr& value) + { + return !items.Contains(value.Obj()); + } + + void DataColumns::AfterInsert(vint index, const Ptr& value) + { + value->dataProvider = dataProvider; + } + + void DataColumns::BeforeRemove(vint index, const Ptr& value) + { + value->dataProvider = nullptr; + } + + DataColumns::DataColumns(DataProvider* _dataProvider) + :dataProvider(_dataProvider) + { + } + + DataColumns::~DataColumns() + { + } + +/*********************************************************************** +DataProvider +***********************************************************************/ + + void DataProvider::NotifyAllItemsUpdate() + { + InvokeOnItemModified(0, Count(), Count()); + } + + void DataProvider::NotifyAllColumnsUpdate() + { + if (columnItemViewCallback) + { + columnItemViewCallback->OnColumnChanged(); + } + } + + GuiListControl::IItemProvider* DataProvider::GetItemProvider() + { + return this; + } + + void DataProvider::OnProcessorChanged() + { + RebuildFilter(); + ReorderRows(true); + } + + void DataProvider::OnItemSourceModified(vint start, vint count, vint newCount) + { + if (!currentSorter && !currentFilter && count == newCount) + { + InvokeOnItemModified(start, count, newCount); + } + else + { + ReorderRows(true); + } + } + + ListViewDataColumns& DataProvider::GetDataColumns() + { + return dataColumns; + } + + DataColumns& DataProvider::GetColumns() + { + return columns; + } + + Ptr DataProvider::GetItemSource() + { + return itemSource; + } + + void DataProvider::SetItemSource(Ptr _itemSource) + { + vint oldCount = 0; + if (itemSource) + { + oldCount = itemSource->GetCount(); + } + if (itemChangedEventHandler) + { + auto ol = itemSource.Cast(); + ol->ItemChanged.Remove(itemChangedEventHandler); + } + + itemSource = nullptr; + itemChangedEventHandler = nullptr; + + if (_itemSource) + { + if (auto ol = _itemSource.Cast()) + { + itemSource = ol; + itemChangedEventHandler = ol->ItemChanged.Add([this](vint start, vint oldCount, vint newCount) + { + OnItemSourceModified(start, oldCount, newCount); + }); + } + else if (auto rl = _itemSource.Cast()) + { + itemSource = rl; + } + else + { + itemSource = IValueList::Create(GetLazyList(_itemSource)); + } + } + + OnItemSourceModified(0, oldCount, itemSource ? itemSource->GetCount() : 0); + } + + void DataProvider::RebuildFilter() + { + if (currentFilter) + { + currentFilter->SetCallback(nullptr); + currentFilter = nullptr; + } + + List> selectedFilters; + CopyFrom( + selectedFilters, + From(columns) + .Select([](Ptr column) {return column->GetFilter(); }) + .Where([](Ptr filter) {return filter != nullptr; }) + ); + if (additionalFilter) + { + selectedFilters.Add(additionalFilter); + } + if (selectedFilters.Count() > 0) + { + auto andFilter = MakePtr(); + FOREACH(Ptr, filter, selectedFilters) + { + andFilter->AddSubFilter(filter); + } + currentFilter = andFilter; + } + + if (currentFilter) + { + currentFilter->SetCallback(this); + } + } + + void DataProvider::ReorderRows(bool invokeCallback) + { + vint oldRowCount = virtualRowToSourceRow.Count(); + virtualRowToSourceRow.Clear(); + vint rowCount = itemSource ? itemSource->GetCount() : 0; + + if (currentFilter) + { + for (vint i = 0; i < rowCount; i++) + { + if (currentFilter->Filter(itemSource->Get(i))) + { + virtualRowToSourceRow.Add(i); + } + } + } + else + { + for (vint i = 0; i < rowCount; i++) + { + virtualRowToSourceRow.Add(i); + } + } + + if (currentSorter && virtualRowToSourceRow.Count() > 0) + { + IDataSorter* sorter = currentSorter.Obj(); + SortLambda( + &virtualRowToSourceRow[0], + virtualRowToSourceRow.Count(), + [=](vint a, vint b) + { + return sorter->Compare(itemSource->Get(a), itemSource->Get(b)); + }); + } + + if (invokeCallback) + { + NotifyAllItemsUpdate(); + } + } + + DataProvider::DataProvider() + :dataColumns(this) + , columns(this) + { + RebuildFilter(); + ReorderRows(false); + } + + DataProvider::~DataProvider() + { + } + + Ptr DataProvider::GetAdditionalFilter() + { + return additionalFilter; + } + + void DataProvider::SetAdditionalFilter(Ptr value) + { + additionalFilter = value; + RebuildFilter(); + ReorderRows(true); + } + + // ===================== GuiListControl::IItemProvider ===================== + + vint DataProvider::Count() + { + return virtualRowToSourceRow.Count(); + } + + WString DataProvider::GetTextValue(vint itemIndex) + { + return GetText(itemIndex); + } + + description::Value DataProvider::GetBindingValue(vint itemIndex) + { + return itemSource ? itemSource->Get(virtualRowToSourceRow[itemIndex]) : Value(); + } + + IDescriptable* DataProvider::RequestView(const WString& identifier) + { + if (identifier == IListViewItemView::Identifier) + { + return (IListViewItemView*)this; + } + else if (identifier == ListViewColumnItemArranger::IColumnItemView::Identifier) + { + return (ListViewColumnItemArranger::IColumnItemView*)this; + } + else if (identifier == IDataGridView::Identifier) + { + return (IDataGridView*)this; + } + else + { + return nullptr; + } + } + + // ===================== list::IListViewItemProvider ===================== + + Ptr DataProvider::GetSmallImage(vint itemIndex) + { + if (0 <= itemIndex && itemIndex < Count()) + { + return ReadProperty(GetBindingValue(itemIndex), smallImageProperty); + } + return nullptr; + } + + Ptr DataProvider::GetLargeImage(vint itemIndex) + { + if (0 <= itemIndex && itemIndex < Count()) + { + return ReadProperty(GetBindingValue(itemIndex), largeImageProperty); + } + return nullptr; + } + + WString DataProvider::GetText(vint itemIndex) + { + if (columns.Count() == 0)return L""; + return columns[0]->GetCellText(itemIndex); + } + + WString DataProvider::GetSubItem(vint itemIndex, vint index) + { + return columns[index + 1]->GetCellText(itemIndex); + } + + vint DataProvider::GetDataColumnCount() + { + return dataColumns.Count(); + } + + vint DataProvider::GetDataColumn(vint index) + { + return dataColumns[index]; + } + + vint DataProvider::GetColumnCount() + { + return columns.Count(); + } + + WString DataProvider::GetColumnText(vint index) + { + return columns[index]->GetText(); + } + + // ===================== list::ListViewColumnItemArranger::IColumnItemView ===================== + + bool DataProvider::AttachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) + { + if (columnItemViewCallback)return false; + columnItemViewCallback = value; + return true; + } + + bool DataProvider::DetachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) + { + if (!columnItemViewCallback) return false; + columnItemViewCallback = nullptr; + return true; + } + + vint DataProvider::GetColumnSize(vint index) + { + return columns[index]->GetSize(); + } + + void DataProvider::SetColumnSize(vint index, vint value) + { + columns[index]->SetSize(value); + } + + GuiMenu* DataProvider::GetDropdownPopup(vint index) + { + return columns[index]->GetPopup(); + } + + ColumnSortingState DataProvider::GetSortingState(vint index) + { + return columns[index]->sortingState; + } + + // ===================== list::IDataGridView ===================== + + bool DataProvider::IsColumnSortable(vint column) + { + return columns[column]->GetSorter(); + } + + void DataProvider::SortByColumn(vint column, bool ascending) + { + if (0 <= column && column < columns.Count()) + { + auto sorter = columns[column]->GetSorter(); + if (!sorter) + { + currentSorter = nullptr; + } + else if (ascending) + { + currentSorter = sorter; + } + else + { + Ptr reverseSorter = new DataReverseSorter(); + reverseSorter->SetSubSorter(sorter); + currentSorter = reverseSorter; + } + } + else + { + currentSorter = nullptr; + } + + for (vint i = 0; i < columns.Count(); i++) + { + columns[i]->sortingState = + i != column ? ColumnSortingState::NotSorted : + ascending ? ColumnSortingState::Ascending : + ColumnSortingState::Descending + ; + } + NotifyAllColumnsUpdate(); + ReorderRows(true); + } + + vint DataProvider::GetSortedColumn() + { + for (vint i = 0; i < columns.Count(); i++) + { + auto state = columns[i]->sortingState; + if (state != ColumnSortingState::NotSorted) + { + return i; + } + } + return -1; + } + + bool DataProvider::IsSortOrderAscending() + { + for (vint i = 0; i < columns.Count(); i++) + { + auto state = columns[i]->sortingState; + if (state != ColumnSortingState::NotSorted) + { + return state == ColumnSortingState::Ascending; + } + } + return true; + } + + vint DataProvider::GetCellSpan(vint row, vint column) + { + return 1; + } + + IDataVisualizerFactory* DataProvider::GetCellDataVisualizerFactory(vint row, vint column) + { + return columns[column]->GetVisualizerFactory().Obj(); + } + + IDataEditorFactory* DataProvider::GetCellDataEditorFactory(vint row, vint column) + { + return columns[column]->GetEditorFactory().Obj(); + } + + description::Value DataProvider::GetBindingCellValue(vint row, vint column) + { + return columns[column]->GetCellValue(row); + } + + void DataProvider::SetBindingCellValue(vint row, vint column, const description::Value& value) + { + columns[column]->SetCellValue(row, value); + } + } + +/*********************************************************************** +GuiBindableDataGrid +***********************************************************************/ + + GuiBindableDataGrid::GuiBindableDataGrid(theme::ThemeName themeName) + :GuiVirtualDataGrid(themeName, new list::DataProvider) + { + dataProvider = dynamic_cast(GetItemProvider()); + } + + GuiBindableDataGrid::~GuiBindableDataGrid() + { + } + + list::ListViewDataColumns& GuiBindableDataGrid::GetDataColumns() + { + return dataProvider->GetDataColumns(); + } + + list::DataColumns& GuiBindableDataGrid::GetColumns() + { + return dataProvider->GetColumns(); + } + + Ptr GuiBindableDataGrid::GetItemSource() + { + return dataProvider->GetItemSource(); + } + + void GuiBindableDataGrid::SetItemSource(Ptr _itemSource) + { + dataProvider->SetItemSource(_itemSource); + } + + Ptr GuiBindableDataGrid::GetAdditionalFilter() + { + return dataProvider->GetAdditionalFilter(); + } + + void GuiBindableDataGrid::SetAdditionalFilter(Ptr value) + { + dataProvider->SetAdditionalFilter(value); + } + + ItemProperty> GuiBindableDataGrid::GetLargeImageProperty() + { + return dataProvider->largeImageProperty; + } + + void GuiBindableDataGrid::SetLargeImageProperty(const ItemProperty>& value) + { + if (dataProvider->largeImageProperty != value) + { + dataProvider->largeImageProperty = value; + dataProvider->NotifyAllItemsUpdate(); + LargeImagePropertyChanged.Execute(GetNotifyEventArguments()); + } + } + + ItemProperty> GuiBindableDataGrid::GetSmallImageProperty() + { + return dataProvider->smallImageProperty; + } + + void GuiBindableDataGrid::SetSmallImageProperty(const ItemProperty>& value) + { + if (dataProvider->smallImageProperty != value) + { + dataProvider->smallImageProperty = value; + dataProvider->NotifyAllItemsUpdate(); + SmallImagePropertyChanged.Execute(GetNotifyEventArguments()); + } + } + + description::Value GuiBindableDataGrid::GetSelectedRowValue() + { + auto pos = GetSelectedCell(); + if (pos.row == -1 || pos.column == -1) + { + return Value(); + } + return dataProvider->GetBindingValue(GetSelectedCell().row); + } + + description::Value GuiBindableDataGrid::GetSelectedCellValue() + { + auto pos = GetSelectedCell(); + if (pos.row == -1 || pos.column == -1) + { + return Value(); + } + return dataProvider->GetColumns()[pos.column]->GetCellValue(pos.row); + } + } + } +} + +/*********************************************************************** +.\CONTROLS\LISTCONTROLPACKAGE\GUIBINDABLELISTCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace list; + using namespace tree; + using namespace reflection::description; + using namespace templates; + +/*********************************************************************** +GuiBindableTextList::ItemSource +***********************************************************************/ + + GuiBindableTextList::ItemSource::ItemSource() + { + } + + GuiBindableTextList::ItemSource::~ItemSource() + { + SetItemSource(nullptr); + } + + Ptr GuiBindableTextList::ItemSource::GetItemSource() + { + return itemSource; + } + + void GuiBindableTextList::ItemSource::SetItemSource(Ptr _itemSource) + { + vint oldCount = 0; + if (itemSource) + { + oldCount = itemSource->GetCount(); + } + if (itemChangedEventHandler) + { + auto ol = itemSource.Cast(); + ol->ItemChanged.Remove(itemChangedEventHandler); + } + + itemSource = nullptr; + itemChangedEventHandler = nullptr; + + if (_itemSource) + { + if (auto ol = _itemSource.Cast()) + { + itemSource = ol; + itemChangedEventHandler = ol->ItemChanged.Add([this](vint start, vint oldCount, vint newCount) + { + InvokeOnItemModified(start, oldCount, newCount); + }); + } + else if (auto rl = _itemSource.Cast()) + { + itemSource = rl; + } + else + { + itemSource = IValueList::Create(GetLazyList(_itemSource)); + } + } + + InvokeOnItemModified(0, oldCount, itemSource ? itemSource->GetCount() : 0); + } + + description::Value GuiBindableTextList::ItemSource::Get(vint index) + { + if (!itemSource) return Value(); + return itemSource->Get(index); + } + + void GuiBindableTextList::ItemSource::UpdateBindingProperties() + { + InvokeOnItemModified(0, Count(), Count()); + } + + // ===================== GuiListControl::IItemProvider ===================== + + vint GuiBindableTextList::ItemSource::Count() + { + if (!itemSource) return 0; + return itemSource->GetCount(); + } + + WString GuiBindableTextList::ItemSource::GetTextValue(vint itemIndex) + { + if (itemSource) + { + if (0 <= itemIndex && itemIndex < itemSource->GetCount()) + { + return ReadProperty(itemSource->Get(itemIndex), textProperty); + } + } + return L""; + } + + IDescriptable* GuiBindableTextList::ItemSource::RequestView(const WString& identifier) + { + if (identifier == ITextItemView::Identifier) + { + return (ITextItemView*)this; + } + else + { + return 0; + } + } + + // ===================== GuiListControl::IItemBindingView ===================== + + description::Value GuiBindableTextList::ItemSource::GetBindingValue(vint itemIndex) + { + if (itemSource) + { + if (0 <= itemIndex && itemIndex < itemSource->GetCount()) + { + return itemSource->Get(itemIndex); + } + } + return Value(); + } + + // ===================== list::TextItemStyleProvider::ITextItemView ===================== + + bool GuiBindableTextList::ItemSource::GetChecked(vint itemIndex) + { + if (itemSource) + { + if (0 <= itemIndex && itemIndex < itemSource->GetCount()) + { + return ReadProperty(itemSource->Get(itemIndex), checkedProperty); + } + } + return false; + } + + void GuiBindableTextList::ItemSource::SetChecked(vint itemIndex, bool value) + { + if (itemSource) + { + if (0 <= itemIndex && itemIndex < itemSource->GetCount()) + { + auto thisValue = itemSource->Get(itemIndex); + WriteProperty(thisValue, checkedProperty, value); + InvokeOnItemModified(itemIndex, 1, 1); + } + } + } + +/*********************************************************************** +GuiBindableTextList +***********************************************************************/ + + GuiBindableTextList::GuiBindableTextList(theme::ThemeName themeName) + :GuiVirtualTextList(themeName, new ItemSource) + { + itemSource = dynamic_cast(GetItemProvider()); + + TextPropertyChanged.SetAssociatedComposition(boundsComposition); + TextPropertyChanged.SetAssociatedComposition(boundsComposition); + } + + GuiBindableTextList::~GuiBindableTextList() + { + } + + Ptr GuiBindableTextList::GetItemSource() + { + return itemSource->GetItemSource(); + } + + void GuiBindableTextList::SetItemSource(Ptr _itemSource) + { + itemSource->SetItemSource(_itemSource); + } + + ItemProperty GuiBindableTextList::GetTextProperty() + { + return itemSource->textProperty; + } + + void GuiBindableTextList::SetTextProperty(const ItemProperty& value) + { + if (itemSource->textProperty != value) + { + itemSource->textProperty = value; + itemSource->UpdateBindingProperties(); + TextPropertyChanged.Execute(GetNotifyEventArguments()); + } + } + + WritableItemProperty GuiBindableTextList::GetCheckedProperty() + { + return itemSource->checkedProperty; + } + + void GuiBindableTextList::SetCheckedProperty(const WritableItemProperty& value) + { + if (itemSource->checkedProperty != value) + { + itemSource->checkedProperty = value; + itemSource->UpdateBindingProperties(); + CheckedPropertyChanged.Execute(GetNotifyEventArguments()); + } + } + + description::Value GuiBindableTextList::GetSelectedItem() + { + vint index = GetSelectedItemIndex(); + if (index == -1) return Value(); + return itemSource->Get(index); + } + +/*********************************************************************** +GuiBindableListView::ItemSource +***********************************************************************/ + + GuiBindableListView::ItemSource::ItemSource() + :columns(this) + , dataColumns(this) + { + } + + GuiBindableListView::ItemSource::~ItemSource() + { + SetItemSource(nullptr); + } + + Ptr GuiBindableListView::ItemSource::GetItemSource() + { + return itemSource; + } + + void GuiBindableListView::ItemSource::SetItemSource(Ptr _itemSource) + { + vint oldCount = 0; + if (itemSource) + { + oldCount = itemSource->GetCount(); + } + if (itemChangedEventHandler) + { + auto ol = itemSource.Cast(); + ol->ItemChanged.Remove(itemChangedEventHandler); + } + + itemSource = nullptr; + itemChangedEventHandler = nullptr; + + if (_itemSource) + { + if (auto ol = _itemSource.Cast()) + { + itemSource = ol; + itemChangedEventHandler = ol->ItemChanged.Add([this](vint start, vint oldCount, vint newCount) + { + InvokeOnItemModified(start, oldCount, newCount); + }); + } + else if (auto rl = _itemSource.Cast()) + { + itemSource = rl; + } + else + { + itemSource = IValueList::Create(GetLazyList(_itemSource)); + } + } + + InvokeOnItemModified(0, oldCount, itemSource ? itemSource->GetCount() : 0); + } + + description::Value GuiBindableListView::ItemSource::Get(vint index) + { + if (!itemSource) return Value(); + return itemSource->Get(index); + } + + void GuiBindableListView::ItemSource::UpdateBindingProperties() + { + InvokeOnItemModified(0, Count(), Count()); + } + + bool GuiBindableListView::ItemSource::NotifyUpdate(vint start, vint count) + { + if (!itemSource) return false; + if (start<0 || start >= itemSource->GetCount() || count <= 0 || start + count > itemSource->GetCount()) + { + return false; + } + else + { + InvokeOnItemModified(start, count, count); + return true; + } + } + + list::ListViewDataColumns& GuiBindableListView::ItemSource::GetDataColumns() + { + return dataColumns; + } + + list::ListViewColumns& GuiBindableListView::ItemSource::GetColumns() + { + return columns; + } + + // ===================== list::IListViewItemProvider ===================== + + void GuiBindableListView::ItemSource::NotifyAllItemsUpdate() + { + NotifyUpdate(0, Count()); + } + + void GuiBindableListView::ItemSource::NotifyAllColumnsUpdate() + { + for (vint i = 0; i < columnItemViewCallbacks.Count(); i++) + { + columnItemViewCallbacks[i]->OnColumnChanged(); + } + } + + // ===================== GuiListControl::IItemProvider ===================== + + vint GuiBindableListView::ItemSource::Count() + { + if (!itemSource) return 0; + return itemSource->GetCount(); + } + + WString GuiBindableListView::ItemSource::GetTextValue(vint itemIndex) + { + return GetText(itemIndex); + } + + description::Value GuiBindableListView::ItemSource::GetBindingValue(vint itemIndex) + { + if (itemSource) + { + if (0 <= itemIndex && itemIndex < itemSource->GetCount()) + { + return itemSource->Get(itemIndex); + } + } + return Value(); + } + + IDescriptable* GuiBindableListView::ItemSource::RequestView(const WString& identifier) + { + if (identifier == IListViewItemView::Identifier) + { + return (IListViewItemView*)this; + } + else if (identifier == ListViewColumnItemArranger::IColumnItemView::Identifier) + { + return (ListViewColumnItemArranger::IColumnItemView*)this; + } + else + { + return 0; + } + } + + // ===================== list::ListViewItemStyleProvider::IListViewItemView ===================== + + Ptr GuiBindableListView::ItemSource::GetSmallImage(vint itemIndex) + { + if (itemSource) + { + if (0 <= itemIndex && itemIndex < itemSource->GetCount()) + { + return ReadProperty(itemSource->Get(itemIndex), smallImageProperty); + } + } + return nullptr; + } + + Ptr GuiBindableListView::ItemSource::GetLargeImage(vint itemIndex) + { + if (itemSource) + { + if (0 <= itemIndex && itemIndex < itemSource->GetCount()) + { + return ReadProperty(itemSource->Get(itemIndex), largeImageProperty); + } + } + return nullptr; + } + + WString GuiBindableListView::ItemSource::GetText(vint itemIndex) + { + if (itemSource) + { + if (0 <= itemIndex && itemIndex < itemSource->GetCount() && columns.Count()>0) + { + return ReadProperty(itemSource->Get(itemIndex), columns[0]->GetTextProperty()); + } + } + return L""; + } + + WString GuiBindableListView::ItemSource::GetSubItem(vint itemIndex, vint index) + { + if (itemSource) + { + if (0 <= itemIndex && itemIndex < itemSource->GetCount() && 0 <= index && index < columns.Count() - 1) + { + return ReadProperty(itemSource->Get(itemIndex), columns[index + 1]->GetTextProperty()); + } + } + return L""; + } + + vint GuiBindableListView::ItemSource::GetDataColumnCount() + { + return dataColumns.Count(); + } + + vint GuiBindableListView::ItemSource::GetDataColumn(vint index) + { + return dataColumns[index]; + } + + vint GuiBindableListView::ItemSource::GetColumnCount() + { + return columns.Count(); + } + + WString GuiBindableListView::ItemSource::GetColumnText(vint index) + { + if (index < 0 || index >= columns.Count()) + { + return L""; + } + else + { + return columns[index]->GetText(); + } + } + + // ===================== list::ListViewColumnItemArranger::IColumnItemView ===================== + + bool GuiBindableListView::ItemSource::AttachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) + { + if(columnItemViewCallbacks.Contains(value)) + { + return false; + } + else + { + columnItemViewCallbacks.Add(value); + return true; + } + } + + bool GuiBindableListView::ItemSource::DetachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) + { + vint index = columnItemViewCallbacks.IndexOf(value); + if (index == -1) + { + return false; + } + else + { + columnItemViewCallbacks.Remove(value); + return true; + } + } + + vint GuiBindableListView::ItemSource::GetColumnSize(vint index) + { + if (index < 0 || index >= columns.Count()) + { + return 0; + } + else + { + return columns[index]->GetSize(); + } + } + + void GuiBindableListView::ItemSource::SetColumnSize(vint index, vint value) + { + if (index >= 0 && index < columns.Count()) + { + columns[index]->SetSize(value); + } + } + + GuiMenu* GuiBindableListView::ItemSource::GetDropdownPopup(vint index) + { + if (index < 0 || index >= columns.Count()) + { + return 0; + } + else + { + return columns[index]->GetDropdownPopup(); + } + } + + ColumnSortingState GuiBindableListView::ItemSource::GetSortingState(vint index) + { + if (index < 0 || index >= columns.Count()) + { + return ColumnSortingState::NotSorted; + } + else + { + return columns[index]->GetSortingState(); + } + } + +/*********************************************************************** +GuiBindableListView +***********************************************************************/ + + GuiBindableListView::GuiBindableListView(theme::ThemeName themeName) + :GuiVirtualListView(themeName, new ItemSource) + { + itemSource = dynamic_cast(GetItemProvider()); + + LargeImagePropertyChanged.SetAssociatedComposition(boundsComposition); + SmallImagePropertyChanged.SetAssociatedComposition(boundsComposition); + } + + GuiBindableListView::~GuiBindableListView() + { + } + + list::ListViewDataColumns& GuiBindableListView::GetDataColumns() + { + return itemSource->GetDataColumns(); + } + + list::ListViewColumns& GuiBindableListView::GetColumns() + { + return itemSource->GetColumns(); + } + + Ptr GuiBindableListView::GetItemSource() + { + return itemSource->GetItemSource(); + } + + void GuiBindableListView::SetItemSource(Ptr _itemSource) + { + itemSource->SetItemSource(_itemSource); + } + + ItemProperty> GuiBindableListView::GetLargeImageProperty() + { + return itemSource->largeImageProperty; + } + + void GuiBindableListView::SetLargeImageProperty(const ItemProperty>& value) + { + if (itemSource->largeImageProperty != value) + { + itemSource->largeImageProperty = value; + itemSource->UpdateBindingProperties(); + LargeImagePropertyChanged.Execute(GetNotifyEventArguments()); + } + } + + ItemProperty> GuiBindableListView::GetSmallImageProperty() + { + return itemSource->smallImageProperty; + } + + void GuiBindableListView::SetSmallImageProperty(const ItemProperty>& value) + { + if (itemSource->smallImageProperty != value) + { + itemSource->smallImageProperty = value; + itemSource->UpdateBindingProperties(); + SmallImagePropertyChanged.Execute(GetNotifyEventArguments()); + } + } + + description::Value GuiBindableListView::GetSelectedItem() + { + vint index = GetSelectedItemIndex(); + if (index == -1) return Value(); + return itemSource->Get(index); + } + +/*********************************************************************** +GuiBindableTreeView::ItemSourceNode +***********************************************************************/ + + void GuiBindableTreeView::ItemSourceNode::PrepareChildren() + { + if (!childrenVirtualList) + { + if (auto value = ReadProperty(itemSource, rootProvider->childrenProperty)) + { + if (auto ol = value.Cast()) + { + itemChangedEventHandler = ol->ItemChanged.Add([this](vint start, vint oldCount, vint newCount) + { + callback->OnBeforeItemModified(this, start, oldCount, newCount); + children.RemoveRange(start, oldCount); + for (vint i = 0; i < newCount; i++) + { + Value value = childrenVirtualList->Get(start + i); + auto node = new ItemSourceNode(value, this); + children.Insert(start + i, node); + } + callback->OnAfterItemModified(this, start, oldCount, newCount); + }); + childrenVirtualList = ol; + } + else if (auto rl = value.Cast()) + { + childrenVirtualList = rl; + } + else + { + childrenVirtualList = IValueList::Create(GetLazyList(value)); + } + } + + if (!childrenVirtualList) + { + childrenVirtualList = IValueList::Create(); + } + + vint count = childrenVirtualList->GetCount(); + for (vint i = 0; i < count; i++) + { + Value value = childrenVirtualList->Get(i); + auto node = new ItemSourceNode(value, this); + children.Add(node); + } + } + } + + void GuiBindableTreeView::ItemSourceNode::UnprepareChildren() + { + if (itemChangedEventHandler) + { + auto ol = childrenVirtualList.Cast(); + ol->ItemChanged.Remove(itemChangedEventHandler); + itemChangedEventHandler = nullptr; + } + childrenVirtualList = nullptr; + FOREACH(Ptr, node, children) + { + node->UnprepareChildren(); + } + children.Clear(); + } + + GuiBindableTreeView::ItemSourceNode::ItemSourceNode(const description::Value& _itemSource, ItemSourceNode* _parent) + :itemSource(_itemSource) + , rootProvider(_parent->rootProvider) + , parent(_parent) + , callback(_parent->callback) + { + } + + GuiBindableTreeView::ItemSourceNode::ItemSourceNode(ItemSource* _rootProvider) + :rootProvider(_rootProvider) + , parent(nullptr) + , callback(_rootProvider) + { + } + + GuiBindableTreeView::ItemSourceNode::~ItemSourceNode() + { + } + + description::Value GuiBindableTreeView::ItemSourceNode::GetItemSource() + { + return itemSource; + } + + void GuiBindableTreeView::ItemSourceNode::SetItemSource(const description::Value& _itemSource) + { + vint oldCount = GetChildCount(); + UnprepareChildren(); + itemSource = _itemSource; + vint newCount = GetChildCount(); + callback->OnBeforeItemModified(this, 0, oldCount, newCount); + callback->OnAfterItemModified(this, 0, oldCount, newCount); + } + + bool GuiBindableTreeView::ItemSourceNode::GetExpanding() + { + return this == rootProvider->rootNode.Obj() ? true : expanding; + } + + void GuiBindableTreeView::ItemSourceNode::SetExpanding(bool value) + { + if (this != rootProvider->rootNode.Obj() && expanding != value) + { + expanding = value; + if (expanding) + { + callback->OnItemExpanded(this); + } + else + { + callback->OnItemCollapsed(this); + } + } + } + + vint GuiBindableTreeView::ItemSourceNode::CalculateTotalVisibleNodes() + { + if (!GetExpanding()) + { + return 1; + } + + PrepareChildren(); + vint count = 1; + FOREACH(Ptr, child, children) + { + count += child->CalculateTotalVisibleNodes(); + } + return count; + } + + vint GuiBindableTreeView::ItemSourceNode::GetChildCount() + { + PrepareChildren(); + return children.Count(); + } + + Ptr GuiBindableTreeView::ItemSourceNode::GetParent() + { + return parent; + } + + Ptr GuiBindableTreeView::ItemSourceNode::GetChild(vint index) + { + PrepareChildren(); + if (0 <= index && index < children.Count()) + { + return children[index]; + } + return nullptr; + } + +/*********************************************************************** +GuiBindableTreeView::ItemSource +***********************************************************************/ + + GuiBindableTreeView::ItemSource::ItemSource() + { + rootNode = new ItemSourceNode(this); + } + + GuiBindableTreeView::ItemSource::~ItemSource() + { + } + + description::Value GuiBindableTreeView::ItemSource::GetItemSource() + { + return rootNode->GetItemSource(); + } + + void GuiBindableTreeView::ItemSource::SetItemSource(const description::Value& _itemSource) + { + rootNode->SetItemSource(_itemSource); + } + + void GuiBindableTreeView::ItemSource::UpdateBindingProperties(bool updateChildrenProperty) + { + vint oldCount = rootNode->GetChildCount(); + if (updateChildrenProperty) + { + rootNode->UnprepareChildren(); + } + vint newCount = rootNode->GetChildCount(); + OnBeforeItemModified(rootNode.Obj(), 0, oldCount, newCount); + OnAfterItemModified(rootNode.Obj(), 0, oldCount, newCount); + } + + // ===================== tree::INodeRootProvider ===================== + + Ptr GuiBindableTreeView::ItemSource::GetRootNode() + { + return rootNode; + } + + WString GuiBindableTreeView::ItemSource::GetTextValue(tree::INodeProvider* node) + { + return ReadProperty(GetBindingValue(node), textProperty); + } + + description::Value GuiBindableTreeView::ItemSource::GetBindingValue(tree::INodeProvider* node) + { + if (auto itemSourceNode = dynamic_cast(node)) + { + return itemSourceNode->GetItemSource(); + } + return Value(); + } + + IDescriptable* GuiBindableTreeView::ItemSource::RequestView(const WString& identifier) + { + if(identifier==ITreeViewItemView::Identifier) + { + return (ITreeViewItemView*)this; + } + else + { + return 0; + } + } + + // ===================== tree::ITreeViewItemView ===================== + + Ptr GuiBindableTreeView::ItemSource::GetNodeImage(tree::INodeProvider* node) + { + if (auto itemSourceNode = dynamic_cast(node)) + { + return ReadProperty(itemSourceNode->GetItemSource(), imageProperty); + } + return nullptr; + } + +/*********************************************************************** +GuiBindableTreeView +***********************************************************************/ + + GuiBindableTreeView::GuiBindableTreeView(theme::ThemeName themeName) + :GuiVirtualTreeView(themeName, new ItemSource) + { + itemSource = dynamic_cast(GetNodeRootProvider()); + + TextPropertyChanged.SetAssociatedComposition(boundsComposition); + ImagePropertyChanged.SetAssociatedComposition(boundsComposition); + ChildrenPropertyChanged.SetAssociatedComposition(boundsComposition); + } + + GuiBindableTreeView::~GuiBindableTreeView() + { + } + + description::Value GuiBindableTreeView::GetItemSource() + { + return itemSource->GetItemSource(); + } + + void GuiBindableTreeView::SetItemSource(description::Value _itemSource) + { + itemSource->SetItemSource(_itemSource); + } + + ItemProperty GuiBindableTreeView::GetTextProperty() + { + return itemSource->textProperty; + } + + void GuiBindableTreeView::SetTextProperty(const ItemProperty& value) + { + if (itemSource->textProperty != value) + { + itemSource->textProperty = value; + itemSource->UpdateBindingProperties(false); + TextPropertyChanged.Execute(GetNotifyEventArguments()); + } + } + + ItemProperty> GuiBindableTreeView::GetImageProperty() + { + return itemSource->imageProperty; + } + + void GuiBindableTreeView::SetImageProperty(const ItemProperty>& value) + { + if (itemSource->imageProperty != value) + { + itemSource->imageProperty = value; + itemSource->UpdateBindingProperties(false); + ImagePropertyChanged.Execute(GetNotifyEventArguments()); + } + } + + ItemProperty> GuiBindableTreeView::GetChildrenProperty() + { + return itemSource->childrenProperty; + } + + void GuiBindableTreeView::SetChildrenProperty(const ItemProperty>& value) + { + if (itemSource->childrenProperty != value) + { + itemSource->childrenProperty = value; + itemSource->UpdateBindingProperties(true); + ChildrenPropertyChanged.Execute(GetNotifyEventArguments()); + } + } + + description::Value GuiBindableTreeView::GetSelectedItem() + { + vint index = GetSelectedItemIndex(); + if (index == -1) return Value(); + + Value result; + if (auto node = nodeItemView->RequestNode(index)) + { + if (auto itemSourceNode = node.Cast()) + { + result = itemSourceNode->GetItemSource(); + } + } + return result; + } + } + } +} + + +/*********************************************************************** +.\CONTROLS\LISTCONTROLPACKAGE\GUICOMBOCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + +/*********************************************************************** +GuiComboBoxBase::CommandExecutor +***********************************************************************/ + + GuiComboBoxBase::CommandExecutor::CommandExecutor(GuiComboBoxBase* _combo) + :combo(_combo) + { + } + + GuiComboBoxBase::CommandExecutor::~CommandExecutor() + { + } + + void GuiComboBoxBase::CommandExecutor::SelectItem() + { + combo->SelectItem(); + } + +/*********************************************************************** +GuiComboBoxBase +***********************************************************************/ + + void GuiComboBoxBase::BeforeControlTemplateUninstalled_() + { + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + ct->SetCommands(nullptr); + } + + void GuiComboBoxBase::AfterControlTemplateInstalled_(bool initialize) + { + GetControlTemplateObject(true)->SetCommands(commandExecutor.Obj()); + } + + bool GuiComboBoxBase::IsAltAvailable() + { + return false; + } + + IGuiMenuService::Direction GuiComboBoxBase::GetSubMenuDirection() + { + return IGuiMenuService::Horizontal; + } + + void GuiComboBoxBase::SelectItem() + { + ItemSelected.Execute(GetNotifyEventArguments()); + } + + void GuiComboBoxBase::OnBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + Size size=GetPreferredMenuClientSize(); + size.x=boundsComposition->GetBounds().Width(); + SetPreferredMenuClientSize(size); + } + + GuiComboBoxBase::GuiComboBoxBase(theme::ThemeName themeName) + :GuiMenuButton(themeName) + { + commandExecutor = new CommandExecutor(this); + + CreateSubMenu(); + SetCascadeAction(false); + + boundsComposition->BoundsChanged.AttachMethod(this, &GuiComboBoxBase::OnBoundsChanged); + } + + GuiComboBoxBase::~GuiComboBoxBase() + { + } + +/*********************************************************************** +GuiComboBoxListControl +***********************************************************************/ + + void GuiComboBoxListControl::BeforeControlTemplateUninstalled() + { + GuiComboBoxBase::BeforeControlTemplateUninstalled(); + } + + void GuiComboBoxListControl::AfterControlTemplateInstalled(bool initialize) + { + GuiComboBoxBase::AfterControlTemplateInstalled(initialize); + GetControlTemplateObject(true)->SetTextVisible(!itemStyleProperty); + } + + bool GuiComboBoxListControl::IsAltAvailable() + { + return true; + } + + void GuiComboBoxListControl::OnActiveAlt() + { + GuiMenuButton::OnActiveAlt(); + GetSubMenu()->GetNativeWindow()->SetFocus(); + containedListControl->SetFocus(); + } + + void GuiComboBoxListControl::RemoveStyleController() + { + if (itemStyleController) + { + SafeDeleteComposition(itemStyleController); + itemStyleController = nullptr; + } + } + + void GuiComboBoxListControl::InstallStyleController(vint itemIndex) + { + if (itemStyleProperty) + { + if (itemIndex != -1) + { + auto item = containedListControl->GetItemProvider()->GetBindingValue(itemIndex); + if (!item.IsNull()) + { + if (auto style = itemStyleProperty(item)) + { + itemStyleController = style; + itemStyleController->SetText(GetText()); + itemStyleController->SetFont(GetFont()); + itemStyleController->SetContext(GetContext()); + itemStyleController->SetVisuallyEnabled(GetVisuallyEnabled()); + itemStyleController->SetAlignmentToParent(Margin(0, 0, 0, 0)); + containerComposition->AddChild(itemStyleController); + } + } + } + } + } + + void GuiComboBoxListControl::DisplaySelectedContent(vint itemIndex) + { + if(itemIndex==-1) + { + SetText(L""); + } + else + { + WString text = containedListControl->GetItemProvider()->GetTextValue(itemIndex); + SetText(text); + GetSubMenu()->Hide(); + } + + RemoveStyleController(); + InstallStyleController(itemIndex); + } + + void GuiComboBoxListControl::AdoptSubMenuSize() + { + Size expectedSize(0, GetFont().size * 20); + Size adoptedSize = containedListControl->GetAdoptedSize(expectedSize); + + Size clientSize = GetPreferredMenuClientSize(); + clientSize.y = adoptedSize.y + GetSubMenu()->GetClientSize().y - containedListControl->GetBoundsComposition()->GetBounds().Height(); + SetPreferredMenuClientSize(clientSize); + + if (GetSubMenuOpening()) + { + GetSubMenu()->SetClientSize(clientSize); + } + } + + void GuiComboBoxListControl::OnTextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (itemStyleController) + { + itemStyleController->SetText(GetText()); + } + } + + void GuiComboBoxListControl::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (itemStyleController) + { + itemStyleController->SetFont(GetFont()); + } + AdoptSubMenuSize(); + } + + void GuiComboBoxListControl::OnContextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (itemStyleController) + { + itemStyleController->SetContext(GetContext()); + } + AdoptSubMenuSize(); + } + + void GuiComboBoxListControl::OnVisuallyEnabledChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (itemStyleController) + { + itemStyleController->SetVisuallyEnabled(GetVisuallyEnabled()); + } + } + + void GuiComboBoxListControl::OnListControlAdoptedSizeInvalidated(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + AdoptSubMenuSize(); + } + + void GuiComboBoxListControl::OnListControlBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + auto flag = flagDisposed; + GetApplication()->InvokeLambdaInMainThread(GetRelatedControlHost(), [=]() + { + if (!*flag.Obj()) + { + AdoptSubMenuSize(); + } + }); + } + + void GuiComboBoxListControl::OnListControlSelectionChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + DisplaySelectedContent(GetSelectedIndex()); + SelectItem(); + SelectedIndexChanged.Execute(GetNotifyEventArguments()); + } + + void GuiComboBoxListControl::OnAttached(GuiListControl::IItemProvider* provider) + { + } + + void GuiComboBoxListControl::OnItemModified(vint start, vint count, vint newCount) + { + vint index = GetSelectedIndex(); + if (start <= index && index < start + count) + { + DisplaySelectedContent(index); + } + } + + GuiComboBoxListControl::GuiComboBoxListControl(theme::ThemeName themeName, GuiSelectableListControl* _containedListControl) + :GuiComboBoxBase(themeName) + , containedListControl(_containedListControl) + { + TextChanged.AttachMethod(this, &GuiComboBoxListControl::OnTextChanged); + FontChanged.AttachMethod(this, &GuiComboBoxListControl::OnFontChanged); + ContextChanged.AttachMethod(this, &GuiComboBoxListControl::OnContextChanged); + VisuallyEnabledChanged.AttachMethod(this, &GuiComboBoxListControl::OnVisuallyEnabledChanged); + + containedListControl->GetItemProvider()->AttachCallback(this); + containedListControl->SetMultiSelect(false); + containedListControl->AdoptedSizeInvalidated.AttachMethod(this, &GuiComboBoxListControl::OnListControlAdoptedSizeInvalidated); + containedListControl->SelectionChanged.AttachMethod(this, &GuiComboBoxListControl::OnListControlSelectionChanged); + boundsChangedHandler = containedListControl->GetBoundsComposition()->BoundsChanged.AttachMethod(this, &GuiComboBoxListControl::OnListControlBoundsChanged); + + auto itemProvider = containedListControl->GetItemProvider(); + + SelectedIndexChanged.SetAssociatedComposition(boundsComposition); + + containedListControl->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + GetSubMenu()->GetContainerComposition()->AddChild(containedListControl->GetBoundsComposition()); + SetFont(GetFont()); + } + + GuiComboBoxListControl::~GuiComboBoxListControl() + { + containedListControl->GetItemProvider()->DetachCallback(this); + containedListControl->GetBoundsComposition()->BoundsChanged.Detach(boundsChangedHandler); + boundsChangedHandler = nullptr; + } + + GuiSelectableListControl* GuiComboBoxListControl::GetContainedListControl() + { + return containedListControl; + } + + GuiComboBoxListControl::ItemStyleProperty GuiComboBoxListControl::GetItemTemplate() + { + return itemStyleProperty; + } + + void GuiComboBoxListControl::SetItemTemplate(ItemStyleProperty value) + { + RemoveStyleController(); + itemStyleProperty = value; + GetControlTemplateObject(true)->SetTextVisible(!itemStyleProperty); + InstallStyleController(GetSelectedIndex()); + ItemTemplateChanged.Execute(GetNotifyEventArguments()); + } + + vint GuiComboBoxListControl::GetSelectedIndex() + { + if(containedListControl->GetSelectedItems().Count()==1) + { + return containedListControl->GetSelectedItems()[0]; + } + else + { + return -1; + } + } + + void GuiComboBoxListControl::SetSelectedIndex(vint value) + { + containedListControl->SetSelected(value, true); + } + + description::Value GuiComboBoxListControl::GetSelectedItem() + { + auto selectedIndex = GetSelectedIndex(); + if (selectedIndex != -1) + { + return containedListControl->GetItemProvider()->GetBindingValue(selectedIndex); + } + return description::Value(); + } + + GuiListControl::IItemProvider* GuiComboBoxListControl::GetItemProvider() + { + return containedListControl->GetItemProvider(); + } + } + } +} + + +/*********************************************************************** +.\CONTROLS\LISTCONTROLPACKAGE\GUIDATAGRIDCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + namespace list + { + using namespace compositions; + using namespace collections; + using namespace description; + using namespace templates; + + const wchar_t* const IDataGridView::Identifier = L"vl::presentation::controls::list::IDataGridView"; + +/*********************************************************************** +DefaultDataGridItemTemplate +***********************************************************************/ + + IDataVisualizerFactory* DefaultDataGridItemTemplate::GetDataVisualizerFactory(vint row, vint column) + { + if (auto dataGrid = dynamic_cast(listControl)) + { + if (auto factory = dataGrid->dataGridView->GetCellDataVisualizerFactory(row, column)) + { + return factory; + } + + if (column == 0) + { + return dataGrid->defaultMainColumnVisualizerFactory.Obj(); + } + else + { + return dataGrid->defaultSubColumnVisualizerFactory.Obj(); + } + + } + + return nullptr; + } + + IDataEditorFactory* DefaultDataGridItemTemplate::GetDataEditorFactory(vint row, vint column) + { + if (auto dataGrid = dynamic_cast(listControl)) + { + return dataGrid->dataGridView->GetCellDataEditorFactory(row, column); + } + return nullptr; + } + + vint DefaultDataGridItemTemplate::GetCellColumnIndex(compositions::GuiGraphicsComposition* composition) + { + for (vint i = 0; i < textTable->GetColumns(); i++) + { + auto cell = textTable->GetSitedCell(0, i); + if (composition == cell) + { + return i; + } + } + return -1; + } + + void DefaultDataGridItemTemplate::OnCellButtonUp(compositions::GuiGraphicsComposition* sender, bool openEditor) + { + if (auto dataGrid = dynamic_cast(listControl)) + { + vint index = GetCellColumnIndex(sender); + if (index != -1) + { + if (currentEditor && dataGrid->GetSelectedCell().column == index) + { + return; + } + + vint currentRow = GetIndex(); + dataGrid->StartEdit(currentRow, index); + } + } + } + + bool DefaultDataGridItemTemplate::IsInEditor(compositions::GuiMouseEventArgs& arguments) + { + if (auto dataGrid = dynamic_cast(listControl)) + { + if (!dataGrid->currentEditor) return false; + auto editorComposition = dataGrid->currentEditor->GetTemplate(); + auto currentComposition = arguments.eventSource; + + while (currentComposition) + { + if (currentComposition == editorComposition) + { + arguments.handled = true; + return true; + } + else if (currentComposition == this) + { + break; + } + else + { + currentComposition = currentComposition->GetParent(); + } + } + + } + return false; + } + + void DefaultDataGridItemTemplate::OnCellButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + IsInEditor(arguments); + } + + void DefaultDataGridItemTemplate::OnCellLeftButtonUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if (auto dataGrid = dynamic_cast(listControl)) + { + if (!IsInEditor(arguments)) + { + if (dataGrid->GetVisuallyEnabled()) + { + OnCellButtonUp(sender, true); + } + } + } + } + + void DefaultDataGridItemTemplate::OnCellRightButtonUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if (auto dataGrid = dynamic_cast(listControl)) + { + if (!IsInEditor(arguments)) + { + if (dataGrid->GetVisuallyEnabled()) + { + OnCellButtonUp(sender, false); + } + } + } + } + + void DefaultDataGridItemTemplate::OnColumnChanged() + { + UpdateSubItemSize(); + } + + void DefaultDataGridItemTemplate::OnInitialize() + { + DefaultListViewItemTemplate::OnInitialize(); + { + textTable = new GuiTableComposition; + textTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); + textTable->SetRowsAndColumns(1, 1); + textTable->SetRowOption(0, GuiCellOption::MinSizeOption()); + textTable->SetColumnOption(0, GuiCellOption::AbsoluteOption(0)); + AddChild(textTable); + } + + if (auto dataGrid = dynamic_cast(listControl)) + { + vint columnCount = dataGrid->listViewItemView->GetColumnCount(); + vint itemIndex = GetIndex(); + + dataVisualizers.Resize(columnCount); + for (vint i = 0; i < dataVisualizers.Count(); i++) + { + auto factory = GetDataVisualizerFactory(itemIndex, i); + dataVisualizers[i] = factory->CreateVisualizer(dataGrid); + } + + textTable->SetRowsAndColumns(1, columnCount); + for (vint i = 0; i < columnCount; i++) + { + auto cell = new GuiCellComposition; + textTable->AddChild(cell); + cell->SetSite(0, i, 1, 1); + cell->GetEventReceiver()->leftButtonDown.AttachMethod(this, &DefaultDataGridItemTemplate::OnCellButtonDown); + cell->GetEventReceiver()->rightButtonDown.AttachMethod(this, &DefaultDataGridItemTemplate::OnCellButtonDown); + cell->GetEventReceiver()->leftButtonUp.AttachMethod(this, &DefaultDataGridItemTemplate::OnCellLeftButtonUp); + cell->GetEventReceiver()->rightButtonUp.AttachMethod(this, &DefaultDataGridItemTemplate::OnCellRightButtonUp); + + auto composition = dataVisualizers[i]->GetTemplate(); + composition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + cell->AddChild(composition); + } + + for (vint i = 0; i < dataVisualizers.Count(); i++) + { + dataVisualizers[i]->BeforeVisualizeCell(dataGrid->GetItemProvider(), itemIndex, i); + } + + GridPos selectedCell = dataGrid->GetSelectedCell(); + if (selectedCell.row == itemIndex) + { + NotifySelectCell(selectedCell.column); + } + else + { + NotifySelectCell(-1); + } + UpdateSubItemSize(); + } + + SelectedChanged.AttachMethod(this, &DefaultDataGridItemTemplate::OnSelectedChanged); + FontChanged.AttachMethod(this, &DefaultDataGridItemTemplate::OnFontChanged); + ContextChanged.AttachMethod(this, &DefaultDataGridItemTemplate::OnContextChanged); + + SelectedChanged.Execute(compositions::GuiEventArgs(this)); + FontChanged.Execute(compositions::GuiEventArgs(this)); + ContextChanged.Execute(compositions::GuiEventArgs(this)); + } + + void DefaultDataGridItemTemplate::OnSelectedChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (!GetSelected()) + { + NotifySelectCell(-1); + } + } + + void DefaultDataGridItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + FOREACH(Ptr, visualizer, dataVisualizers) + { + visualizer->GetTemplate()->SetFont(GetFont()); + } + if (currentEditor) + { + currentEditor->GetTemplate()->SetFont(GetFont()); + } + } + + void DefaultDataGridItemTemplate::OnContextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + FOREACH(Ptr, visualizer, dataVisualizers) + { + visualizer->GetTemplate()->SetContext(GetContext()); + } + if (currentEditor) + { + currentEditor->GetTemplate()->SetContext(GetContext()); + } + } + + DefaultDataGridItemTemplate::DefaultDataGridItemTemplate() + { + } + + DefaultDataGridItemTemplate::~DefaultDataGridItemTemplate() + { + FOREACH(Ptr, visualizer, dataVisualizers) + { + visualizer->NotifyDeletedTemplate(); + } + if (currentEditor) + { + currentEditor->NotifyDeletedTemplate(); + } + } + + void DefaultDataGridItemTemplate::UpdateSubItemSize() + { + if (auto dataGrid = dynamic_cast(listControl)) + { + vint columnCount = dataGrid->listViewItemView->GetColumnCount(); + if (columnCount > textTable->GetColumns()) + { + columnCount = textTable->GetColumns(); + } + for (vint i = 0; i < columnCount; i++) + { + textTable->SetColumnOption(i, GuiCellOption::AbsoluteOption(dataGrid->columnItemView->GetColumnSize(i))); + } + textTable->UpdateCellBounds(); + } + } + + bool DefaultDataGridItemTemplate::IsEditorOpened() + { + return currentEditor != nullptr; + } + + void DefaultDataGridItemTemplate::NotifyOpenEditor(vint column, IDataEditor* editor) + { + currentEditor = editor; + if (currentEditor) + { + auto cell = textTable->GetSitedCell(0, column); + auto* editorBounds = currentEditor->GetTemplate(); + editorBounds->SetFont(GetFont()); + editorBounds->SetContext(GetContext()); + if (editorBounds->GetParent() && editorBounds->GetParent() != cell) + { + editorBounds->GetParent()->RemoveChild(editorBounds); + } + editorBounds->SetAlignmentToParent(Margin(0, 0, 0, 0)); + cell->AddChild(editorBounds); + if (auto focusControl = currentEditor->GetTemplate()->GetFocusControl()) + { + focusControl->SetFocus(); + } + } + } + + void DefaultDataGridItemTemplate::NotifyCloseEditor() + { + if (currentEditor) + { + auto composition = currentEditor->GetTemplate(); + if (composition->GetParent()) + { + composition->GetParent()->RemoveChild(composition); + } + currentEditor = nullptr; + } + } + + void DefaultDataGridItemTemplate::NotifySelectCell(vint column) + { + for (vint i = 0; i < dataVisualizers.Count(); i++) + { + dataVisualizers[i]->SetSelected(i == column); + } + } + + void DefaultDataGridItemTemplate::NotifyCellEdited() + { + for (vint i = 0; i < dataVisualizers.Count(); i++) + { + dataVisualizers[i]->BeforeVisualizeCell(listControl->GetItemProvider(), GetIndex(), i); + } + } + } + +/*********************************************************************** +GuiVirtualDataGrid (Editor) +***********************************************************************/ + + using namespace list; + + void GuiVirtualDataGrid::OnItemModified(vint start, vint count, vint newCount) + { + GuiVirtualListView::OnItemModified(start, count, newCount); + if(!GetItemProvider()->IsEditing()) + { + StopEdit(false); + } + } + + void GuiVirtualDataGrid::OnStyleUninstalled(ItemStyle* style) + { + GuiVirtualListView::OnStyleUninstalled(style); + if (auto itemStyle = dynamic_cast(style)) + { + if (itemStyle->IsEditorOpened()) + { + itemStyle->NotifyCloseEditor(); + currentEditor = nullptr; + currentEditorPos = { -1,-1 }; + } + } + } + + void GuiVirtualDataGrid::NotifyCloseEditor() + { + if (currentEditorPos.row != -1 && GetArranger()) + { + auto style = GetArranger()->GetVisibleStyle(currentEditorPos.row); + if (auto itemStyle = dynamic_cast(style)) + { + itemStyle->NotifyCloseEditor(); + } + } + } + + void GuiVirtualDataGrid::NotifySelectCell(vint row, vint column) + { + selectedCell = { row, column }; + SelectedCellChanged.Execute(GetNotifyEventArguments()); + + auto style = GetArranger()->GetVisibleStyle(row); + if (auto itemStyle = dynamic_cast(style)) + { + itemStyle->NotifySelectCell(column); + } + } + + bool GuiVirtualDataGrid::StartEdit(vint row, vint column) + { + StopEdit(true); + NotifySelectCell(row, column); + + auto style = GetArranger()->GetVisibleStyle(row); + if (auto itemStyle = dynamic_cast(style)) + { + if (auto factory = dataGridView->GetCellDataEditorFactory(row, column)) + { + currentEditorOpeningEditor = true; + currentEditorPos = { row,column }; + currentEditor = factory->CreateEditor(this); + currentEditor->BeforeEditCell(GetItemProvider(), row, column); + itemStyle->NotifyOpenEditor(column, currentEditor.Obj()); + currentEditorOpeningEditor = false; + return true; + } + } + return false; + } + + void GuiVirtualDataGrid::StopEdit(bool forOpenNewEditor) + { + if (GetItemProvider()->IsEditing()) + { + NotifyCloseEditor(); + } + else + { + if (currentEditorPos != GridPos{-1, -1}) + { + if (currentEditor) + { + NotifyCloseEditor(); + } + if (!forOpenNewEditor) + { + NotifySelectCell(-1, -1); + } + } + } + currentEditor = nullptr; + currentEditorPos = { -1,-1 }; + } + +/*********************************************************************** +GuiVirtualDataGrid (IDataGridContext) +***********************************************************************/ + + templates::GuiListViewTemplate* GuiVirtualDataGrid::GetListViewControlTemplate() + { + return GetControlTemplateObject(true); + } + + void GuiVirtualDataGrid::RequestSaveData() + { + if (currentEditor && !currentEditorOpeningEditor) + { + GuiControl* focusedControl = nullptr; + if (auto controlHost = GetRelatedControlHost()) + { + if (auto graphicsHost = controlHost->GetGraphicsHost()) + { + if (auto focusComposition = graphicsHost->GetFocusedComposition()) + { + focusedControl = focusComposition->GetRelatedControl(); + } + } + } + + GetItemProvider()->PushEditing(); + dataGridView->SetBindingCellValue(currentEditorPos.row, currentEditorPos.column, currentEditor->GetTemplate()->GetCellValue()); + GetItemProvider()->PopEditing(); + + auto style = GetArranger()->GetVisibleStyle(currentEditorPos.row); + if (auto itemStyle = dynamic_cast(style)) + { + itemStyle->NotifyCellEdited(); + } + + if (currentEditor && focusedControl) + { + focusedControl->SetFocus(); + } + } + } + +/*********************************************************************** +GuiVirtualDataGrid +***********************************************************************/ + + void GuiVirtualDataGrid::OnColumnClicked(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) + { + if(dataGridView->IsColumnSortable(arguments.itemIndex)) + { + switch(columnItemView->GetSortingState(arguments.itemIndex)) + { + case ColumnSortingState::NotSorted: + dataGridView->SortByColumn(arguments.itemIndex, true); + break; + case ColumnSortingState::Ascending: + dataGridView->SortByColumn(arguments.itemIndex, false); + break; + case ColumnSortingState::Descending: + dataGridView->SortByColumn(-1, false); + break; + } + } + } + + GuiVirtualDataGrid::GuiVirtualDataGrid(theme::ThemeName themeName, GuiListControl::IItemProvider* _itemProvider) + :GuiVirtualListView(themeName, _itemProvider) + { + listViewItemView = dynamic_cast(_itemProvider->RequestView(IListViewItemView::Identifier)); + columnItemView = dynamic_cast(_itemProvider->RequestView(ListViewColumnItemArranger::IColumnItemView::Identifier)); + dataGridView = dynamic_cast(_itemProvider->RequestView(IDataGridView::Identifier)); + + { + auto mainProperty = [](const Value&) { return new MainColumnVisualizerTemplate; }; + auto subProperty = [](const Value&) { return new SubColumnVisualizerTemplate; }; + auto cellBorderProperty = [](const Value&) { return new CellBorderVisualizerTemplate; }; + + auto mainFactory = MakePtr(mainProperty); + auto subFactory = MakePtr(subProperty); + defaultMainColumnVisualizerFactory = MakePtr(cellBorderProperty, mainFactory); + defaultSubColumnVisualizerFactory = MakePtr(cellBorderProperty, subFactory); + } + + CHECK_ERROR(listViewItemView != nullptr, L"GuiVirtualDataGrid::GuiVirtualDataGrid(IStyleController*, GuiListControl::IItemProvider*)#Missing IListViewItemView from item provider."); + CHECK_ERROR(columnItemView != nullptr, L"GuiVirtualDataGrid::GuiVirtualDataGrid(IStyleController*, GuiListControl::IItemProvider*)#Missing ListViewColumnItemArranger::IColumnItemView from item provider."); + CHECK_ERROR(dataGridView != nullptr, L"GuiVirtualDataGrid::GuiVirtualDataGrid(IStyleController*, GuiListControl::IItemProvider*)#Missing IDataGridView from item provider."); + + SetViewToDefault(); + ColumnClicked.AttachMethod(this, &GuiVirtualDataGrid::OnColumnClicked); + SelectedCellChanged.SetAssociatedComposition(boundsComposition); + } + + GuiVirtualDataGrid::~GuiVirtualDataGrid() + { + } + + GuiListControl::IItemProvider* GuiVirtualDataGrid::GetItemProvider() + { + return GuiVirtualListView::GetItemProvider(); + } + + void GuiVirtualDataGrid::SetViewToDefault() + { + SetStyleAndArranger( + [](const Value&) { return new list::DefaultDataGridItemTemplate; }, + new list::ListViewColumnItemArranger + ); + } + + GridPos GuiVirtualDataGrid::GetSelectedCell() + { + return selectedCell; + } + + void GuiVirtualDataGrid::SetSelectedCell(const GridPos& value) + { + if (selectedCell == value) + { + return; + } + + bool validPos = 0 <= value.row && value.row < GetItemProvider()->Count() && 0 <= value.column && value.column < listViewItemView->GetColumnCount(); + StopEdit(true); + + if (validPos) + { + NotifySelectCell(value.row, value.column); + } + else + { + NotifySelectCell(-1, -1); + } + + if (validPos) + { + EnsureItemVisible(value.row); + ClearSelection(); + SetSelected(value.row, true); + StartEdit(value.row, value.column); + } + else + { + ClearSelection(); + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\LISTCONTROLPACKAGE\GUIDATAGRIDEXTENSIONS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + namespace list + { + using namespace compositions; + using namespace elements; + using namespace theme; + using namespace templates; + +/*********************************************************************** +DataVisualizerBase +***********************************************************************/ + + DataVisualizerBase::DataVisualizerBase() + { + } + + DataVisualizerBase::~DataVisualizerBase() + { + if (visualizerTemplate) + { + SafeDeleteComposition(visualizerTemplate); + } + } + + IDataVisualizerFactory* DataVisualizerBase::GetFactory() + { + return factory; + } + + templates::GuiGridVisualizerTemplate* DataVisualizerBase::GetTemplate() + { + return visualizerTemplate; + } + + void DataVisualizerBase::NotifyDeletedTemplate() + { + visualizerTemplate = nullptr; + } + + void DataVisualizerBase::BeforeVisualizeCell(GuiListControl::IItemProvider* itemProvider, vint row, vint column) + { + if (auto listViewItemView = dynamic_cast(dataGridContext->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + auto style = dataGridContext->GetListViewControlTemplate(); + visualizerTemplate->SetPrimaryTextColor(style->GetPrimaryTextColor()); + visualizerTemplate->SetSecondaryTextColor(style->GetSecondaryTextColor()); + visualizerTemplate->SetItemSeparatorColor(style->GetItemSeparatorColor()); + + visualizerTemplate->SetLargeImage(listViewItemView->GetLargeImage(row)); + visualizerTemplate->SetSmallImage(listViewItemView->GetSmallImage(row)); + visualizerTemplate->SetText(column == 0 ? listViewItemView->GetText(row) : listViewItemView->GetSubItem(row, column - 1)); + } + if (auto dataGridView = dynamic_cast(dataGridContext->GetItemProvider()->RequestView(IDataGridView::Identifier))) + { + visualizerTemplate->SetRowValue(itemProvider->GetBindingValue(row)); + visualizerTemplate->SetCellValue(dataGridView->GetBindingCellValue(row, column)); + } + } + + void DataVisualizerBase::SetSelected(bool value) + { + if (visualizerTemplate) + { + visualizerTemplate->SetSelected(value); + } + } + +/*********************************************************************** +DataVisualizerFactory +***********************************************************************/ + + DataVisualizerFactory::ItemTemplate* DataVisualizerFactory::CreateItemTemplate(controls::list::IDataGridContext* dataGridContext) + { + ItemTemplate* itemTemplate = templateFactory({}); + CHECK_ERROR(itemTemplate, L"DataVisualizerFactory::CreateItemTemplate(IDataGridContext*)#An instance of GuiGridEditorTemplate is expected."); + if (decoratedFactory) + { + auto childTemplate = decoratedFactory->CreateItemTemplate(dataGridContext); + childTemplate->SetAlignmentToParent(Margin(0, 0, 0, 0)); + itemTemplate->GetContainerComposition()->AddChild(childTemplate); + +#define FORWARD_EVENT(NAME)\ + itemTemplate->NAME##Changed.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments)\ + {\ + childTemplate->Set##NAME(itemTemplate->Get##NAME());\ + });\ + +#define FORWARD_EVENT_IMPL(CLASS, TYPE, NAME, VALUE) FORWARD_EVENT(NAME) + + GuiTemplate_PROPERTIES(FORWARD_EVENT_IMPL) + GuiControlTemplate_PROPERTIES(FORWARD_EVENT_IMPL) + GuiGridCellTemplate_PROPERTIES(FORWARD_EVENT_IMPL) + GuiGridVisualizerTemplate_PROPERTIES(FORWARD_EVENT_IMPL) + +#undef FORWARD_EVENT_IMPL +#undef FORWARD_EVENT + } + return itemTemplate; + } + + DataVisualizerFactory::DataVisualizerFactory(TemplateProperty _templateFactory, Ptr _decoratedFactory) + :templateFactory(_templateFactory) + , decoratedFactory(_decoratedFactory) + { + } + + DataVisualizerFactory::~DataVisualizerFactory() + { + } + + Ptr DataVisualizerFactory::CreateVisualizer(controls::list::IDataGridContext* dataGridContext) + { + auto dataVisualizer = MakePtr(); + dataVisualizer->factory = this; + dataVisualizer->dataGridContext = dataGridContext; + dataVisualizer->visualizerTemplate = CreateItemTemplate(dataGridContext); + + return dataVisualizer; + } + +/*********************************************************************** +DataEditorBase +***********************************************************************/ + + void DataEditorBase::OnCellValueChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + dataGridContext->RequestSaveData(); + } + + DataEditorBase::DataEditorBase() + { + } + + DataEditorBase::~DataEditorBase() + { + if (editorTemplate) + { + SafeDeleteComposition(editorTemplate); + } + } + + IDataEditorFactory* DataEditorBase::GetFactory() + { + return factory; + } + + templates::GuiGridEditorTemplate* DataEditorBase::GetTemplate() + { + return editorTemplate; + } + + void DataEditorBase::NotifyDeletedTemplate() + { + editorTemplate = nullptr; + } + + void DataEditorBase::BeforeEditCell(GuiListControl::IItemProvider* itemProvider, vint row, vint column) + { + if (auto listViewItemView = dynamic_cast(dataGridContext->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + auto style = dataGridContext->GetListViewControlTemplate(); + editorTemplate->SetPrimaryTextColor(style->GetPrimaryTextColor()); + editorTemplate->SetSecondaryTextColor(style->GetSecondaryTextColor()); + editorTemplate->SetItemSeparatorColor(style->GetItemSeparatorColor()); + + editorTemplate->SetLargeImage(listViewItemView->GetLargeImage(row)); + editorTemplate->SetSmallImage(listViewItemView->GetSmallImage(row)); + editorTemplate->SetText(column == 0 ? listViewItemView->GetText(row) : listViewItemView->GetSubItem(row, column - 1)); + } + if (auto dataGridView = dynamic_cast(dataGridContext->GetItemProvider()->RequestView(IDataGridView::Identifier))) + { + editorTemplate->SetRowValue(itemProvider->GetBindingValue(row)); + editorTemplate->SetCellValue(dataGridView->GetBindingCellValue(row, column)); + } + editorTemplate->CellValueChanged.AttachMethod(this, &DataEditorBase::OnCellValueChanged); + } + + bool DataEditorBase::GetCellValueSaved() + { + if (editorTemplate) + { + return editorTemplate->GetCellValueSaved(); + } + return true; + } + +/*********************************************************************** +DataEditorFactory +***********************************************************************/ + + DataEditorFactory::DataEditorFactory(TemplateProperty _templateFactory) + :templateFactory(_templateFactory) + { + } + + DataEditorFactory::~DataEditorFactory() + { + } + + Ptr DataEditorFactory::CreateEditor(controls::list::IDataGridContext* dataGridContext) + { + auto editor = MakePtr(); + editor->factory = this; + editor->dataGridContext = dataGridContext; + + ItemTemplate* itemTemplate = templateFactory({}); + CHECK_ERROR(itemTemplate, L"DataEditorFactory::CreateEditor(IDataGridContext*)#An instance of GuiGridEditorTemplate is expected."); + editor->editorTemplate = itemTemplate; + return editor; + } + +/*********************************************************************** +MainColumnVisualizerTemplate +***********************************************************************/ + + void MainColumnVisualizerTemplate::OnTextChanged(GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetText(GetText()); + } + + void MainColumnVisualizerTemplate::OnFontChanged(GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetFont(GetFont()); + } + + void MainColumnVisualizerTemplate::OnTextColorChanged(GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetColor(GetPrimaryTextColor()); + } + + void MainColumnVisualizerTemplate::OnSmallImageChanged(GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + auto imageData = GetSmallImage(); + if (imageData) + { + image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); + } + else + { + image->SetImage(nullptr); + } + } + + MainColumnVisualizerTemplate::MainColumnVisualizerTemplate() + { + GuiTableComposition* table = new GuiTableComposition; + table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + table->SetRowsAndColumns(3, 2); + table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); + table->SetRowOption(1, GuiCellOption::MinSizeOption()); + table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); + table->SetColumnOption(0, GuiCellOption::MinSizeOption()); + table->SetColumnOption(1, GuiCellOption::PercentageOption(1.0)); + table->SetCellPadding(2); + { + GuiCellComposition* cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(1, 0, 1, 1); + cell->SetPreferredMinSize(Size(16, 16)); + + image = GuiImageFrameElement::Create(); + image->SetStretch(true); + cell->SetOwnedElement(image); + } + { + GuiCellComposition* cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 1, 3, 1); + cell->SetMargin(Margin(0, 0, 8, 0)); + + text = GuiSolidLabelElement::Create(); + text->SetAlignments(Alignment::Left, Alignment::Center); + text->SetEllipse(true); + cell->SetOwnedElement(text); + } + table->SetAlignmentToParent(Margin(0, 0, 0, 0)); + + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + AddChild(table); + + TextChanged.AttachMethod(this, &MainColumnVisualizerTemplate::OnTextChanged); + FontChanged.AttachMethod(this, &MainColumnVisualizerTemplate::OnFontChanged); + PrimaryTextColorChanged.AttachMethod(this, &MainColumnVisualizerTemplate::OnTextColorChanged); + SmallImageChanged.AttachMethod(this, &MainColumnVisualizerTemplate::OnSmallImageChanged); + + TextChanged.Execute(compositions::GuiEventArgs(this)); + FontChanged.Execute(compositions::GuiEventArgs(this)); + PrimaryTextColorChanged.Execute(compositions::GuiEventArgs(this)); + SmallImageChanged.Execute(compositions::GuiEventArgs(this)); + } + + MainColumnVisualizerTemplate::~MainColumnVisualizerTemplate() + { + } + +/*********************************************************************** +SubColumnVisualizerTemplate +***********************************************************************/ + + void SubColumnVisualizerTemplate::OnTextChanged(GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetText(GetText()); + } + + void SubColumnVisualizerTemplate::OnFontChanged(GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetFont(GetFont()); + } + + void SubColumnVisualizerTemplate::OnTextColorChanged(GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetColor(GetSecondaryTextColor()); + } + + void SubColumnVisualizerTemplate::Initialize(bool fixTextColor) + { + text = GuiSolidLabelElement::Create(); + text->SetVerticalAlignment(Alignment::Center); + + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + SetMargin(Margin(8, 0, 8, 0)); + SetOwnedElement(text); + + TextChanged.AttachMethod(this, &SubColumnVisualizerTemplate::OnTextChanged); + FontChanged.AttachMethod(this, &SubColumnVisualizerTemplate::OnFontChanged); + if (!fixTextColor) + { + SecondaryTextColorChanged.AttachMethod(this, &SubColumnVisualizerTemplate::OnTextColorChanged); + } + + TextChanged.Execute(compositions::GuiEventArgs(this)); + FontChanged.Execute(compositions::GuiEventArgs(this)); + if (!fixTextColor) + { + SecondaryTextColorChanged.Execute(compositions::GuiEventArgs(this)); + } + } + + SubColumnVisualizerTemplate::SubColumnVisualizerTemplate(bool fixTextColor) + { + Initialize(fixTextColor); + } + + SubColumnVisualizerTemplate::SubColumnVisualizerTemplate() + { + Initialize(false); + } + + SubColumnVisualizerTemplate::~SubColumnVisualizerTemplate() + { + } + +/*********************************************************************** +HyperlinkVisualizerTemplate +***********************************************************************/ + + void HyperlinkVisualizerTemplate::label_MouseEnter(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + FontProperties font = text->GetFont(); + font.underline = true; + text->SetFont(font); + } + + void HyperlinkVisualizerTemplate::label_MouseLeave(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + FontProperties font = text->GetFont(); + font.underline = false; + text->SetFont(font); + } + + HyperlinkVisualizerTemplate::HyperlinkVisualizerTemplate() + :SubColumnVisualizerTemplate(true) + { + text->SetColor(Color(0, 0, 255)); + text->SetEllipse(true); + GetEventReceiver()->mouseEnter.AttachMethod(this, &HyperlinkVisualizerTemplate::label_MouseEnter); + GetEventReceiver()->mouseLeave.AttachMethod(this, &HyperlinkVisualizerTemplate::label_MouseLeave); + SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::Hand)); + } + + HyperlinkVisualizerTemplate::~HyperlinkVisualizerTemplate() + { + } + +/*********************************************************************** +CellBorderVisualizerTemplate +***********************************************************************/ + + void CellBorderVisualizerTemplate::OnItemSeparatorColorChanged(GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + border1->SetColor(GetItemSeparatorColor()); + border2->SetColor(GetItemSeparatorColor()); + } + + CellBorderVisualizerTemplate::CellBorderVisualizerTemplate() + { + GuiBoundsComposition* bounds1 = nullptr; + GuiBoundsComposition* bounds2 = nullptr; + { + border1 = GuiSolidBorderElement::Create(); + + bounds1 = new GuiBoundsComposition; + bounds1->SetOwnedElement(border1); + bounds1->SetAlignmentToParent(Margin(-1, 0, 0, 0)); + } + { + border2 = GuiSolidBorderElement::Create(); + + bounds2 = new GuiBoundsComposition; + bounds2->SetOwnedElement(border2); + bounds2->SetAlignmentToParent(Margin(0, -1, 0, 0)); + } + + SetAlignmentToParent(Margin(0, 0, 1, 1)); + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + AddChild(bounds1); + AddChild(bounds2); + + ItemSeparatorColorChanged.AttachMethod(this, &CellBorderVisualizerTemplate::OnItemSeparatorColorChanged); + + ItemSeparatorColorChanged.Execute(compositions::GuiEventArgs(this)); + } + + CellBorderVisualizerTemplate::~CellBorderVisualizerTemplate() + { + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\LISTCONTROLPACKAGE\GUILISTCONTROLITEMARRANGERS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace elements; + using namespace compositions; + + namespace list + { + +/*********************************************************************** +RangedItemArrangerBase +***********************************************************************/ + + void RangedItemArrangerBase::InvalidateAdoptedSize() + { + if (listControl) + { + listControl->AdoptedSizeInvalidated.Execute(listControl->GetNotifyEventArguments()); + } + } + + vint RangedItemArrangerBase::CalculateAdoptedSize(vint expectedSize, vint count, vint itemSize) + { + vint visibleCount = expectedSize / itemSize; + if (count < visibleCount) + { + visibleCount = count; + } + else if (count > visibleCount) + { + vint deltaA = expectedSize - count * itemSize; + vint deltaB = itemSize - deltaA; + if (deltaB < deltaA) + { + visibleCount++; + } + } + return visibleCount * itemSize; + } + + RangedItemArrangerBase::ItemStyleRecord RangedItemArrangerBase::CreateStyle(vint index) + { + GuiSelectableButton* backgroundButton = nullptr; + if (listControl->GetDisplayItemBackground()) + { + backgroundButton = new GuiSelectableButton(theme::ThemeName::ListItemBackground); + if (auto style = listControl->GetControlTemplateObject(true)->GetBackgroundTemplate()) + { + backgroundButton->SetControlTemplate(style); + } + backgroundButton->SetAutoSelection(false); + } + + auto itemStyle = callback->RequestItem(index, backgroundButton->GetBoundsComposition()); + if (backgroundButton) + { + itemStyle->SetAlignmentToParent(Margin(0, 0, 0, 0)); + itemStyle->SelectedChanged.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) + { + backgroundButton->SetSelected(itemStyle->GetSelected()); + }); + + backgroundButton->SetSelected(itemStyle->GetSelected()); + backgroundButton->GetContainerComposition()->AddChild(itemStyle); + } + return { itemStyle, backgroundButton }; + } + + void RangedItemArrangerBase::DeleteStyle(ItemStyleRecord style) + { + callback->ReleaseItem(style.key); + if (style.value) + { + SafeDeleteControl(style.value); + } + } + + compositions::GuiBoundsComposition* RangedItemArrangerBase::GetStyleBounds(ItemStyleRecord style) + { + return style.value ? style.value->GetBoundsComposition() : style.key; + } + + void RangedItemArrangerBase::ClearStyles() + { + startIndex = 0; + if (callback) + { + for (vint i = 0; i < visibleStyles.Count(); i++) + { + DeleteStyle(visibleStyles[i]); + } + } + visibleStyles.Clear(); + viewBounds = Rect(0, 0, 0, 0); + InvalidateItemSizeCache(); + InvalidateAdoptedSize(); + } + + void RangedItemArrangerBase::OnViewChangedInternal(Rect oldBounds, Rect newBounds) + { + vint endIndex = startIndex + visibleStyles.Count() - 1; + vint newStartIndex = 0; + vint itemCount = itemProvider->Count(); + BeginPlaceItem(true, newBounds, newStartIndex); + if (newStartIndex < 0) newStartIndex = 0; + + StyleList newVisibleStyles; + for (vint i = newStartIndex; i < itemCount; i++) + { + auto style + = startIndex <= i && i <= endIndex + ? visibleStyles[i - startIndex] + : CreateStyle(i) + ; + newVisibleStyles.Add(style); + + Rect bounds; + Margin alignmentToParent; + PlaceItem(true, i, style, newBounds, bounds, alignmentToParent); + if (IsItemOutOfViewBounds(i, style, bounds, newBounds)) + { + break; + } + + bounds.x1 -= newBounds.x1; + bounds.x2 -= newBounds.x1; + bounds.y1 -= newBounds.y1; + bounds.y2 -= newBounds.y1; + } + + vint newEndIndex = newStartIndex + newVisibleStyles.Count() - 1; + for (vint i = 0; i < visibleStyles.Count(); i++) + { + vint index = startIndex + i; + if (index < newStartIndex || index > newEndIndex) + { + DeleteStyle(visibleStyles[i]); + } + } + CopyFrom(visibleStyles, newVisibleStyles); + + if (EndPlaceItem(true, newBounds, newStartIndex)) + { + callback->OnTotalSizeChanged(); + InvalidateAdoptedSize(); + } + startIndex = newStartIndex; + } + + void RangedItemArrangerBase::RearrangeItemBounds() + { + vint newStartIndex = startIndex; + BeginPlaceItem(false, viewBounds, newStartIndex); + for (vint i = 0; i < visibleStyles.Count(); i++) + { + auto style = visibleStyles[i]; + Rect bounds; + Margin alignmentToParent(-1, -1, -1, -1); + PlaceItem(false, startIndex + i, style, viewBounds, bounds, alignmentToParent); + + bounds.x1 -= viewBounds.x1; + bounds.x2 -= viewBounds.x1; + bounds.y1 -= viewBounds.y1; + bounds.y2 -= viewBounds.y1; + + callback->SetStyleAlignmentToParent(GetStyleBounds(style), alignmentToParent); + callback->SetStyleBounds(GetStyleBounds(style), bounds); + } + EndPlaceItem(false, viewBounds, startIndex); + } + + RangedItemArrangerBase::RangedItemArrangerBase() + { + } + + RangedItemArrangerBase::~RangedItemArrangerBase() + { + } + + void RangedItemArrangerBase::OnAttached(GuiListControl::IItemProvider* provider) + { + itemProvider = provider; + if (provider) + { + OnItemModified(0, 0, provider->Count()); + } + } + + void RangedItemArrangerBase::OnItemModified(vint start, vint count, vint newCount) + { + if (callback && !itemProvider->IsEditing()) + { + suppressOnViewChanged = true; + { + vint visibleCount = visibleStyles.Count(); + vint itemCount = itemProvider->Count(); + SortedList reusedStyles; + for (vint i = 0; i < visibleCount; i++) + { + vint index = startIndex + i; + if (index >= itemCount) + { + break; + } + + vint oldIndex = -1; + if (index < start) + { + oldIndex = index; + } + else if (index >= start + newCount) + { + oldIndex = index - newCount + count; + } + + if (oldIndex != -1) + { + if (oldIndex >= startIndex && oldIndex < startIndex + visibleCount) + { + auto style = visibleStyles[oldIndex - startIndex]; + reusedStyles.Add(style); + visibleStyles.Add(style); + } + else + { + oldIndex = -1; + } + } + if (oldIndex == -1) + { + visibleStyles.Add(CreateStyle(index)); + } + } + + for (vint i = 0; i < visibleCount; i++) + { + auto style = visibleStyles[i]; + if (!reusedStyles.Contains(style)) + { + DeleteStyle(style); + } + } + + visibleStyles.RemoveRange(0, visibleCount); + for (vint i = 0; i < visibleStyles.Count(); i++) + { + visibleStyles[i].key->SetIndex(startIndex + i); + } + } + suppressOnViewChanged = false; + + callback->OnTotalSizeChanged(); + callback->SetViewLocation(viewBounds.LeftTop()); + InvalidateAdoptedSize(); + } + } + + void RangedItemArrangerBase::AttachListControl(GuiListControl* value) + { + listControl = value; + InvalidateAdoptedSize(); + } + + void RangedItemArrangerBase::DetachListControl() + { + listControl = 0; + } + + GuiListControl::IItemArrangerCallback* RangedItemArrangerBase::GetCallback() + { + return callback; + } + + void RangedItemArrangerBase::SetCallback(GuiListControl::IItemArrangerCallback* value) + { + if (callback != value) + { + ClearStyles(); + callback = value; + } + } + + Size RangedItemArrangerBase::GetTotalSize() + { + if (callback) + { + return OnCalculateTotalSize(); + } + else + { + return Size(0, 0); + } + } + + GuiListControl::ItemStyle* RangedItemArrangerBase::GetVisibleStyle(vint itemIndex) + { + if (startIndex <= itemIndex && itemIndex < startIndex + visibleStyles.Count()) + { + return visibleStyles[itemIndex - startIndex].key; + } + else + { + return 0; + } + } + + vint RangedItemArrangerBase::GetVisibleIndex(GuiListControl::ItemStyle* style) + { + for (vint i = 0; i < visibleStyles.Count(); i++) + { + if (visibleStyles[i].key == style) + { + return i + startIndex; + } + } + return -1; + } + + void RangedItemArrangerBase::ReloadVisibleStyles() + { + ClearStyles(); + } + + void RangedItemArrangerBase::OnViewChanged(Rect bounds) + { + if (!suppressOnViewChanged) + { + suppressOnViewChanged = true; + Rect oldBounds = viewBounds; + viewBounds = bounds; + if (callback) + { + OnViewChangedInternal(oldBounds, viewBounds); + RearrangeItemBounds(); + } + suppressOnViewChanged = false; + } + } + +/*********************************************************************** +FixedHeightItemArranger +***********************************************************************/ + + vint FixedHeightItemArranger::GetWidth() + { + return -1; + } + + vint FixedHeightItemArranger::GetYOffset() + { + return 0; + } + + void FixedHeightItemArranger::BeginPlaceItem(bool forMoving, Rect newBounds, vint& newStartIndex) + { + pi_width = GetWidth(); + if (forMoving) + { + pim_rowHeight = rowHeight; + newStartIndex = (newBounds.Top() - GetYOffset()) / rowHeight; + } + } + + void FixedHeightItemArranger::PlaceItem(bool forMoving, vint index, ItemStyleRecord style, Rect viewBounds, Rect& bounds, Margin& alignmentToParent) + { + vint top = GetYOffset() + index * rowHeight; + if (pi_width == -1) + { + alignmentToParent = Margin(0, -1, 0, -1); + bounds = Rect(Point(0, top), Size(0, rowHeight)); + } + else + { + alignmentToParent = Margin(-1, -1, -1, -1); + bounds = Rect(Point(0, top), Size(pi_width, rowHeight)); + } + if (forMoving) + { + vint styleHeight = callback->GetStylePreferredSize(GetStyleBounds(style)).y; + if (pim_rowHeight < styleHeight) + { + pim_rowHeight = styleHeight; + } + } + } + + bool FixedHeightItemArranger::IsItemOutOfViewBounds(vint index, ItemStyleRecord style, Rect bounds, Rect viewBounds) + { + return bounds.Top() >= viewBounds.Bottom(); + } + + bool FixedHeightItemArranger::EndPlaceItem(bool forMoving, Rect newBounds, vint newStartIndex) + { + if (forMoving) + { + if (pim_rowHeight != rowHeight) + { + vint offset = (pim_rowHeight - rowHeight) * newStartIndex; + rowHeight = pim_rowHeight; + callback->SetViewLocation(Point(0, newBounds.Top() + offset)); + return true; + } + } + return false; + } + + void FixedHeightItemArranger::InvalidateItemSizeCache() + { + rowHeight = 1; + } + + Size FixedHeightItemArranger::OnCalculateTotalSize() + { + vint width = GetWidth(); + if (width < 0) width = 0; + return Size(width, rowHeight * itemProvider->Count() + GetYOffset()); + } + + FixedHeightItemArranger::FixedHeightItemArranger() + { + } + + FixedHeightItemArranger::~FixedHeightItemArranger() + { + } + + vint FixedHeightItemArranger::FindItem(vint itemIndex, compositions::KeyDirection key) + { + vint count = itemProvider->Count(); + if (count == 0) return -1; + vint groupCount = viewBounds.Height() / rowHeight; + if (groupCount == 0) groupCount = 1; + switch (key) + { + case KeyDirection::Up: + itemIndex--; + break; + case KeyDirection::Down: + itemIndex++; + break; + case KeyDirection::Home: + itemIndex = 0; + break; + case KeyDirection::End: + itemIndex = count; + break; + case KeyDirection::PageUp: + itemIndex -= groupCount; + break; + case KeyDirection::PageDown: + itemIndex += groupCount; + break; + default: + return -1; + } + + if (itemIndex < 0) return 0; + else if (itemIndex >= count) return count - 1; + else return itemIndex; + } + + bool FixedHeightItemArranger::EnsureItemVisible(vint itemIndex) + { + if (callback) + { + if (itemIndex < 0 || itemIndex >= itemProvider->Count()) + { + return false; + } + while (true) + { + vint yOffset = GetYOffset(); + vint top = itemIndex*rowHeight; + vint bottom = top + rowHeight + yOffset; + + if (viewBounds.Height() < rowHeight) + { + if (viewBounds.Top() < bottom && top < viewBounds.Bottom()) + { + break; + } + } + + Point location = viewBounds.LeftTop(); + if (top < viewBounds.Top()) + { + location.y = top; + } + else if (viewBounds.Bottom() < bottom) + { + location.y = bottom - viewBounds.Height(); + } + else + { + break; + } + callback->SetViewLocation(location); + } + return true; + } + return false; + } + + Size FixedHeightItemArranger::GetAdoptedSize(Size expectedSize) + { + if (itemProvider) + { + vint yOffset = GetYOffset(); + vint y = expectedSize.y - yOffset; + vint itemCount = itemProvider->Count(); + return Size(expectedSize.x, yOffset + CalculateAdoptedSize(y, itemCount, rowHeight)); + } + return expectedSize; + } + +/*********************************************************************** +FixedSizeMultiColumnItemArranger +***********************************************************************/ + + void FixedSizeMultiColumnItemArranger::BeginPlaceItem(bool forMoving, Rect newBounds, vint& newStartIndex) + { + if (forMoving) + { + pim_itemSize = itemSize; + vint rows = newBounds.Top() / itemSize.y; + if (rows < 0) rows = 0; + vint cols = newBounds.Width() / itemSize.x; + if (cols < 1) cols = 1; + newStartIndex = rows * cols; + } + } + + void FixedSizeMultiColumnItemArranger::PlaceItem(bool forMoving, vint index, ItemStyleRecord style, Rect viewBounds, Rect& bounds, Margin& alignmentToParent) + { + vint rowItems = viewBounds.Width() / itemSize.x; + if (rowItems < 1) rowItems = 1; + + vint row = index / rowItems; + vint col = index % rowItems; + bounds = Rect(Point(col * itemSize.x, row * itemSize.y), itemSize); + if (forMoving) + { + Size styleSize = callback->GetStylePreferredSize(GetStyleBounds(style)); + if (pim_itemSize.x < styleSize.x) pim_itemSize.x = styleSize.x; + if (pim_itemSize.y < styleSize.y) pim_itemSize.y = styleSize.y; + } + } + + bool FixedSizeMultiColumnItemArranger::IsItemOutOfViewBounds(vint index, ItemStyleRecord style, Rect bounds, Rect viewBounds) + { + return bounds.Top() >= viewBounds.Bottom(); + } + + bool FixedSizeMultiColumnItemArranger::EndPlaceItem(bool forMoving, Rect newBounds, vint newStartIndex) + { + if (forMoving) + { + if (pim_itemSize != itemSize) + { + itemSize = pim_itemSize; + return true; + } + } + return false; + } + + void FixedSizeMultiColumnItemArranger::CalculateRange(Size itemSize, Rect bounds, vint count, vint& start, vint& end) + { + vint startRow = bounds.Top() / itemSize.y; + if (startRow < 0) startRow = 0; + vint endRow = (bounds.Bottom() - 1) / itemSize.y; + vint cols = bounds.Width() / itemSize.x; + if (cols < 1) cols = 1; + + start = startRow*cols; + end = (endRow + 1)*cols - 1; + if (end >= count) end = count - 1; + } + + void FixedSizeMultiColumnItemArranger::InvalidateItemSizeCache() + { + itemSize = Size(1, 1); + } + + Size FixedSizeMultiColumnItemArranger::OnCalculateTotalSize() + { + vint rowItems = viewBounds.Width() / itemSize.x; + if (rowItems < 1) rowItems = 1; + vint rows = itemProvider->Count() / rowItems; + if (itemProvider->Count() % rowItems) rows++; + + return Size(itemSize.x * rowItems, itemSize.y*rows); + } + + FixedSizeMultiColumnItemArranger::FixedSizeMultiColumnItemArranger() + { + } + + FixedSizeMultiColumnItemArranger::~FixedSizeMultiColumnItemArranger() + { + } + + vint FixedSizeMultiColumnItemArranger::FindItem(vint itemIndex, compositions::KeyDirection key) + { + vint count = itemProvider->Count(); + vint columnCount = viewBounds.Width() / itemSize.x; + if (columnCount == 0) columnCount = 1; + vint rowCount = viewBounds.Height() / itemSize.y; + if (rowCount == 0) rowCount = 1; + + switch (key) + { + case KeyDirection::Up: + itemIndex -= columnCount; + break; + case KeyDirection::Down: + itemIndex += columnCount; + break; + case KeyDirection::Left: + itemIndex--; + break; + case KeyDirection::Right: + itemIndex++; + break; + case KeyDirection::Home: + itemIndex = 0; + break; + case KeyDirection::End: + itemIndex = count; + break; + case KeyDirection::PageUp: + itemIndex -= columnCount*rowCount; + break; + case KeyDirection::PageDown: + itemIndex += columnCount*rowCount; + break; + case KeyDirection::PageLeft: + itemIndex -= itemIndex%columnCount; + break; + case KeyDirection::PageRight: + itemIndex += columnCount - itemIndex%columnCount - 1; + break; + default: + return -1; + } + + if (itemIndex < 0) return 0; + else if (itemIndex >= count) return count - 1; + else return itemIndex; + } + + bool FixedSizeMultiColumnItemArranger::EnsureItemVisible(vint itemIndex) + { + if (callback) + { + if (itemIndex < 0 || itemIndex >= itemProvider->Count()) + { + return false; + } + while (true) + { + vint rowHeight = itemSize.y; + vint columnCount = viewBounds.Width() / itemSize.x; + if (columnCount == 0) columnCount = 1; + vint rowIndex = itemIndex / columnCount; + + vint top = rowIndex*rowHeight; + vint bottom = top + rowHeight; + + if (viewBounds.Height() < rowHeight) + { + if (viewBounds.Top() < bottom && top < viewBounds.Bottom()) + { + break; + } + } + + Point location = viewBounds.LeftTop(); + if (top < viewBounds.Top()) + { + location.y = top; + } + else if (viewBounds.Bottom() < bottom) + { + location.y = bottom - viewBounds.Height(); + } + else + { + break; + } + callback->SetViewLocation(location); + } + return true; + } + return false; + } + + Size FixedSizeMultiColumnItemArranger::GetAdoptedSize(Size expectedSize) + { + if (itemProvider) + { + vint count = itemProvider->Count(); + vint columnCount = viewBounds.Width() / itemSize.x; + vint rowCount = viewBounds.Height() / itemSize.y; + return Size( + CalculateAdoptedSize(expectedSize.x, columnCount, itemSize.x), + CalculateAdoptedSize(expectedSize.y, rowCount, itemSize.y) + ); + } + return expectedSize; + } + +/*********************************************************************** +FixedHeightMultiColumnItemArranger +***********************************************************************/ + + void FixedHeightMultiColumnItemArranger::CalculateRange(vint itemHeight, Rect bounds, vint& rows, vint& startColumn) + { + rows = bounds.Height() / itemHeight; + if (rows < 1) rows = 1; + startColumn = bounds.Left() / bounds.Width(); + } + + void FixedHeightMultiColumnItemArranger::BeginPlaceItem(bool forMoving, Rect newBounds, vint& newStartIndex) + { + pi_currentWidth = 0; + pi_totalWidth = 0; + if (forMoving) + { + pim_itemHeight = itemHeight; + vint rows = newBounds.Height() / itemHeight; + if (rows < 1) rows = 1; + vint columns = newBounds.Left() / newBounds.Width(); + newStartIndex = rows * columns; + } + } + + void FixedHeightMultiColumnItemArranger::PlaceItem(bool forMoving, vint index, ItemStyleRecord style, Rect viewBounds, Rect& bounds, Margin& alignmentToParent) + { + vint rows = viewBounds.Height() / itemHeight; + if (rows < 1) rows = 1; + + vint row = index % rows; + if (row == 0) + { + pi_totalWidth += pi_currentWidth; + pi_currentWidth = 0; + } + + Size styleSize = callback->GetStylePreferredSize(GetStyleBounds(style)); + if (pi_currentWidth < styleSize.x) pi_currentWidth = styleSize.x; + bounds = Rect(Point(pi_totalWidth + viewBounds.Left(), itemHeight * row), Size(0, 0)); + if (forMoving) + { + if (pim_itemHeight < styleSize.y) pim_itemHeight = styleSize.y; + } + } + + bool FixedHeightMultiColumnItemArranger::IsItemOutOfViewBounds(vint index, ItemStyleRecord style, Rect bounds, Rect viewBounds) + { + return bounds.Left() >= viewBounds.Right(); + } + + bool FixedHeightMultiColumnItemArranger::EndPlaceItem(bool forMoving, Rect newBounds, vint newStartIndex) + { + if (forMoving) + { + if (pim_itemHeight != itemHeight) + { + itemHeight = pim_itemHeight; + return true; + } + } + return false; + } + + void FixedHeightMultiColumnItemArranger::InvalidateItemSizeCache() + { + itemHeight = 1; + } + + Size FixedHeightMultiColumnItemArranger::OnCalculateTotalSize() + { + vint rows = viewBounds.Height() / itemHeight; + if (rows < 1) rows = 1; + vint columns = itemProvider->Count() / rows; + if (itemProvider->Count() % rows) columns += 1; + return Size(viewBounds.Width() * columns, 0); + } + + FixedHeightMultiColumnItemArranger::FixedHeightMultiColumnItemArranger() + :itemHeight(1) + { + } + + FixedHeightMultiColumnItemArranger::~FixedHeightMultiColumnItemArranger() + { + } + + vint FixedHeightMultiColumnItemArranger::FindItem(vint itemIndex, compositions::KeyDirection key) + { + vint count = itemProvider->Count(); + vint groupCount = viewBounds.Height() / itemHeight; + if (groupCount == 0) groupCount = 1; + switch (key) + { + case KeyDirection::Up: + itemIndex--; + break; + case KeyDirection::Down: + itemIndex++; + break; + case KeyDirection::Left: + itemIndex -= groupCount; + break; + case KeyDirection::Right: + itemIndex += groupCount; + break; + case KeyDirection::Home: + itemIndex = 0; + break; + case KeyDirection::End: + itemIndex = count; + break; + case KeyDirection::PageUp: + itemIndex -= itemIndex%groupCount; + break; + case KeyDirection::PageDown: + itemIndex += groupCount - itemIndex%groupCount - 1; + break; + default: + return -1; + } + + if (itemIndex < 0) return 0; + else if (itemIndex >= count) return count - 1; + else return itemIndex; + } + + bool FixedHeightMultiColumnItemArranger::EnsureItemVisible(vint itemIndex) + { + if (callback) + { + if (itemIndex < 0 || itemIndex >= itemProvider->Count()) + { + return false; + } + while (true) + { + vint rowCount = viewBounds.Height() / itemHeight; + if (rowCount == 0) rowCount = 1; + vint columnIndex = itemIndex / rowCount; + vint minIndex = startIndex; + vint maxIndex = startIndex + visibleStyles.Count() - 1; + + Point location = viewBounds.LeftTop(); + if (minIndex <= itemIndex && itemIndex <= maxIndex) + { + Rect bounds = callback->GetStyleBounds(GetStyleBounds(visibleStyles[itemIndex - startIndex])); + if (0 < bounds.Bottom() && bounds.Top() < viewBounds.Width() && bounds.Width() > viewBounds.Width()) + { + break; + } + else if (bounds.Left() < 0) + { + location.x -= viewBounds.Width(); + } + else if (bounds.Right() > viewBounds.Width()) + { + location.x += viewBounds.Width(); + } + else + { + break; + } + } + else if (columnIndex < minIndex / rowCount) + { + location.x -= viewBounds.Width(); + } + else if (columnIndex >= maxIndex / rowCount) + { + location.x += viewBounds.Width(); + } + else + { + break; + } + callback->SetViewLocation(location); + } + return true; + } + return false; + } + + Size FixedHeightMultiColumnItemArranger::GetAdoptedSize(Size expectedSize) + { + if (itemProvider) + { + vint count = itemProvider->Count(); + return Size(expectedSize.x, CalculateAdoptedSize(expectedSize.y, count, itemHeight)); + } + return expectedSize; + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\LISTCONTROLPACKAGE\GUILISTCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace elements; + using namespace compositions; + +/*********************************************************************** +GuiListControl::ItemCallback +***********************************************************************/ + + Ptr GuiListControl::ItemCallback::InstallStyle(ItemStyle* style, vint itemIndex, compositions::GuiBoundsComposition* itemComposition) + { + auto handler = style->BoundsChanged.AttachMethod(this, &ItemCallback::OnStyleBoundsChanged); + listControl->GetContainerComposition()->AddChild(itemComposition ? itemComposition : style); + listControl->OnStyleInstalled(itemIndex, style); + return handler; + } + + GuiListControl::ItemStyle* GuiListControl::ItemCallback::UninstallStyle(vint index) + { + auto style = installedStyles.Keys()[index]; + auto handler = installedStyles.Values()[index]; + listControl->OnStyleUninstalled(style); + listControl->GetContainerComposition()->RemoveChild(style); + style->BoundsChanged.Detach(handler); + return style; + } + + void GuiListControl::ItemCallback::OnStyleBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + listControl->InvokeOrDelayIfRendering([=]() + { + listControl->CalculateView(); + }); + } + + GuiListControl::ItemCallback::ItemCallback(GuiListControl* _listControl) + :listControl(_listControl) + { + } + + GuiListControl::ItemCallback::~ItemCallback() + { + ClearCache(); + } + + void GuiListControl::ItemCallback::ClearCache() + { + for (vint i = 0; i < installedStyles.Count(); i++) + { + auto style = UninstallStyle(i); + SafeDeleteComposition(style); + } + installedStyles.Clear(); + } + + void GuiListControl::ItemCallback::OnAttached(IItemProvider* provider) + { + itemProvider = provider; + } + + void GuiListControl::ItemCallback::OnItemModified(vint start, vint count, vint newCount) + { + listControl->OnItemModified(start, count, newCount); + } + + GuiListControl::ItemStyle* GuiListControl::ItemCallback::RequestItem(vint itemIndex, compositions::GuiBoundsComposition* itemComposition) + { + CHECK_ERROR(0 <= itemIndex && itemIndex < itemProvider->Count(), L"GuiListControl::ItemCallback::RequestItem(vint)#Index out of range."); + CHECK_ERROR(listControl->itemStyleProperty, L"GuiListControl::ItemCallback::RequestItem(vint)#SetItemTemplate function should be called before adding items to the list control."); + + auto style = listControl->itemStyleProperty(itemProvider->GetBindingValue(itemIndex)); + auto handler = InstallStyle(style, itemIndex, itemComposition); + installedStyles.Add(style, handler); + return style; + } + + void GuiListControl::ItemCallback::ReleaseItem(ItemStyle* style) + { + vint index = installedStyles.Keys().IndexOf(style); + if (index != -1) + { + auto style = UninstallStyle(index); + installedStyles.Remove(style); + SafeDeleteComposition(style); + } + } + + void GuiListControl::ItemCallback::SetViewLocation(Point value) + { + Rect virtualRect(value, listControl->GetViewSize()); + Rect realRect = listControl->axis->VirtualRectToRealRect(listControl->fullSize, virtualRect); + listControl->SetViewPosition(realRect.LeftTop()); + } + + Size GuiListControl::ItemCallback::GetStylePreferredSize(compositions::GuiBoundsComposition* style) + { + Size size = style->GetPreferredBounds().GetSize(); + return listControl->axis->RealSizeToVirtualSize(size); + } + + void GuiListControl::ItemCallback::SetStyleAlignmentToParent(compositions::GuiBoundsComposition* style, Margin margin) + { + Margin newMargin = listControl->axis->VirtualMarginToRealMargin(margin); + style->SetAlignmentToParent(newMargin); + } + + Rect GuiListControl::ItemCallback::GetStyleBounds(compositions::GuiBoundsComposition* style) + { + Rect bounds = style->GetBounds(); + return listControl->axis->RealRectToVirtualRect(listControl->GetViewSize(), bounds); + } + + void GuiListControl::ItemCallback::SetStyleBounds(compositions::GuiBoundsComposition* style, Rect bounds) + { + Rect newBounds = listControl->axis->VirtualRectToRealRect(listControl->GetViewSize(), bounds); + return style->SetBounds(newBounds); + } + + compositions::GuiGraphicsComposition* GuiListControl::ItemCallback::GetContainerComposition() + { + return listControl->GetContainerComposition(); + } + + void GuiListControl::ItemCallback::OnTotalSizeChanged() + { + listControl->CalculateView(); + } + +/*********************************************************************** +GuiListControl +***********************************************************************/ + + void GuiListControl::BeforeControlTemplateUninstalled_() + { + } + + void GuiListControl::AfterControlTemplateInstalled_(bool initialize) + { + if (itemArranger) + { + itemArranger->ReloadVisibleStyles(); + CalculateView(); + } + } + + void GuiListControl::OnItemModified(vint start, vint count, vint newCount) + { + } + + void GuiListControl::OnStyleInstalled(vint itemIndex, ItemStyle* style) + { + style->SetFont(GetFont()); + style->SetContext(GetContext()); + style->SetText(itemProvider->GetTextValue(itemIndex)); + style->SetVisuallyEnabled(GetVisuallyEnabled()); + style->SetSelected(false); + style->SetIndex(itemIndex); + style->Initialize(this); + AttachItemEvents(style); + } + + void GuiListControl::OnStyleUninstalled(ItemStyle* style) + { + DetachItemEvents(style); + } + + void GuiListControl::OnRenderTargetChanged(elements::IGuiGraphicsRenderTarget* renderTarget) + { + SetStyleAndArranger(itemStyleProperty, itemArranger); + GuiScrollView::OnRenderTargetChanged(renderTarget); + } + + void GuiListControl::OnBeforeReleaseGraphicsHost() + { + GuiScrollView::OnBeforeReleaseGraphicsHost(); + SetStyleAndArranger({}, nullptr); + } + + Size GuiListControl::QueryFullSize() + { + Size virtualSize = itemArranger ? itemArranger->GetTotalSize() : Size(0, 0); + fullSize = axis->VirtualSizeToRealSize(virtualSize); + return fullSize; + } + + void GuiListControl::UpdateView(Rect viewBounds) + { + if (itemArranger) + { + Rect newBounds = axis->RealRectToVirtualRect(fullSize, viewBounds); + itemArranger->OnViewChanged(newBounds); + } + } + + void GuiListControl::OnBoundsMouseButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(GetVisuallyEnabled()) + { + boundsComposition->GetRelatedGraphicsHost()->SetFocus(boundsComposition); + } + } + + void GuiListControl::SetStyleAndArranger(ItemStyleProperty styleProperty, Ptr arranger) + { + if (itemArranger) + { + itemArranger->DetachListControl(); + itemArranger->SetCallback(nullptr); + itemProvider->DetachCallback(itemArranger.Obj()); + } + callback->ClearCache(); + + itemStyleProperty = styleProperty; + itemArranger = arranger; + + if (auto scroll = GetVerticalScroll()) + { + scroll->SetPosition(0); + } + if (auto scroll = GetHorizontalScroll()) + { + scroll->SetPosition(0); + } + + if (itemArranger) + { + itemProvider->AttachCallback(itemArranger.Obj()); + itemArranger->SetCallback(callback.Obj()); + itemArranger->AttachListControl(this); + } + CalculateView(); + } + + void GuiListControl::OnClientBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + auto args = GetNotifyEventArguments(); + AdoptedSizeInvalidated.Execute(args); + } + + void GuiListControl::OnVisuallyEnabledChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + FOREACH(ItemStyle*, style, visibleStyles.Keys()) + { + style->SetVisuallyEnabled(GetVisuallyEnabled()); + } + } + + void GuiListControl::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + FOREACH(ItemStyle*, style, visibleStyles.Keys()) + { + style->SetFont(GetFont()); + } + } + + void GuiListControl::OnContextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + FOREACH(ItemStyle*, style, visibleStyles.Keys()) + { + style->SetContext(GetContext()); + } + } + + void GuiListControl::OnItemMouseEvent(compositions::GuiItemMouseEvent& itemEvent, ItemStyle* style, compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if (itemArranger && GetVisuallyEnabled()) + { + vint itemIndex = itemArranger->GetVisibleIndex(style); + if (itemIndex != -1) + { + GuiItemMouseEventArgs redirectArguments; + (GuiMouseEventArgs&)redirectArguments = arguments; + redirectArguments.compositionSource = boundsComposition; + redirectArguments.eventSource = boundsComposition; + redirectArguments.itemIndex = itemIndex; + itemEvent.Execute(redirectArguments); + arguments = redirectArguments; + } + } + } + + void GuiListControl::OnItemNotifyEvent(compositions::GuiItemNotifyEvent& itemEvent, ItemStyle* style, compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (itemArranger && GetVisuallyEnabled()) + { + vint itemIndex = itemArranger->GetVisibleIndex(style); + if (itemIndex != -1) + { + GuiItemEventArgs redirectArguments; + (GuiEventArgs&)redirectArguments = arguments; + redirectArguments.compositionSource = boundsComposition; + redirectArguments.eventSource = boundsComposition; + redirectArguments.itemIndex = itemIndex; + itemEvent.Execute(redirectArguments); + arguments = redirectArguments; + } + } + } + +#define ATTACH_ITEM_MOUSE_EVENT(EVENTNAME, ITEMEVENTNAME)\ + {\ + Func func(this, &GuiListControl::OnItemMouseEvent);\ + helper->EVENTNAME##Handler = style->GetEventReceiver()->EVENTNAME.AttachFunction(\ + Curry(Curry(func)(ITEMEVENTNAME))(style)\ + );\ + }\ + +#define ATTACH_ITEM_NOTIFY_EVENT(EVENTNAME, ITEMEVENTNAME)\ + {\ + Func func(this, &GuiListControl::OnItemNotifyEvent);\ + helper->EVENTNAME##Handler = style->GetEventReceiver()->EVENTNAME.AttachFunction(\ + Curry(Curry(func)(ITEMEVENTNAME))(style)\ + );\ + }\ + + void GuiListControl::AttachItemEvents(ItemStyle* style) + { + vint index=visibleStyles.Keys().IndexOf(style); + if(index==-1) + { + Ptr helper=new VisibleStyleHelper; + visibleStyles.Add(style, helper); + + ATTACH_ITEM_MOUSE_EVENT(leftButtonDown, ItemLeftButtonDown); + ATTACH_ITEM_MOUSE_EVENT(leftButtonUp, ItemLeftButtonUp); + ATTACH_ITEM_MOUSE_EVENT(leftButtonDoubleClick, ItemLeftButtonDoubleClick); + ATTACH_ITEM_MOUSE_EVENT(middleButtonDown, ItemMiddleButtonDown); + ATTACH_ITEM_MOUSE_EVENT(middleButtonUp, ItemMiddleButtonUp); + ATTACH_ITEM_MOUSE_EVENT(middleButtonDoubleClick, ItemMiddleButtonDoubleClick); + ATTACH_ITEM_MOUSE_EVENT(rightButtonDown, ItemRightButtonDown); + ATTACH_ITEM_MOUSE_EVENT(rightButtonUp, ItemRightButtonUp); + ATTACH_ITEM_MOUSE_EVENT(rightButtonDoubleClick, ItemRightButtonDoubleClick); + ATTACH_ITEM_MOUSE_EVENT(mouseMove, ItemMouseMove); + ATTACH_ITEM_NOTIFY_EVENT(mouseEnter, ItemMouseEnter); + ATTACH_ITEM_NOTIFY_EVENT(mouseLeave, ItemMouseLeave); + } + } + +#undef ATTACH_ITEM_MOUSE_EVENT +#undef ATTACH_ITEM_NOTIFY_EVENT + +#define DETACH_ITEM_EVENT(EVENTNAME) style->GetEventReceiver()->EVENTNAME.Detach(helper->EVENTNAME##Handler) + + void GuiListControl::DetachItemEvents(ItemStyle* style) + { + vint index=visibleStyles.Keys().IndexOf(style); + if(index!=-1) + { + Ptr helper=visibleStyles.Values().Get(index); + visibleStyles.Remove(style); + + DETACH_ITEM_EVENT(leftButtonDown); + DETACH_ITEM_EVENT(leftButtonUp); + DETACH_ITEM_EVENT(leftButtonDoubleClick); + DETACH_ITEM_EVENT(middleButtonDown); + DETACH_ITEM_EVENT(middleButtonUp); + DETACH_ITEM_EVENT(middleButtonDoubleClick); + DETACH_ITEM_EVENT(rightButtonDown); + DETACH_ITEM_EVENT(rightButtonUp); + DETACH_ITEM_EVENT(rightButtonDoubleClick); + DETACH_ITEM_EVENT(mouseMove); + DETACH_ITEM_EVENT(mouseEnter); + DETACH_ITEM_EVENT(mouseLeave); + } + } + +#undef DETACH_ITEM_EVENT + + GuiListControl::GuiListControl(theme::ThemeName themeName, IItemProvider* _itemProvider, bool acceptFocus) + :GuiScrollView(themeName) + , itemProvider(_itemProvider) + { + FontChanged.AttachMethod(this, &GuiListControl::OnFontChanged); + ContextChanged.AttachMethod(this, &GuiListControl::OnContextChanged); + VisuallyEnabledChanged.AttachMethod(this, &GuiListControl::OnVisuallyEnabledChanged); + containerComposition->BoundsChanged.AttachMethod(this, &GuiListControl::OnClientBoundsChanged); + + ItemTemplateChanged.SetAssociatedComposition(boundsComposition); + ArrangerChanged.SetAssociatedComposition(boundsComposition); + AxisChanged.SetAssociatedComposition(boundsComposition); + AdoptedSizeInvalidated.SetAssociatedComposition(boundsComposition); + + ItemLeftButtonDown.SetAssociatedComposition(boundsComposition); + ItemLeftButtonUp.SetAssociatedComposition(boundsComposition); + ItemLeftButtonDoubleClick.SetAssociatedComposition(boundsComposition); + ItemMiddleButtonDown.SetAssociatedComposition(boundsComposition); + ItemMiddleButtonUp.SetAssociatedComposition(boundsComposition); + ItemMiddleButtonDoubleClick.SetAssociatedComposition(boundsComposition); + ItemRightButtonDown.SetAssociatedComposition(boundsComposition); + ItemRightButtonUp.SetAssociatedComposition(boundsComposition); + ItemRightButtonDoubleClick.SetAssociatedComposition(boundsComposition); + ItemMouseMove.SetAssociatedComposition(boundsComposition); + ItemMouseEnter.SetAssociatedComposition(boundsComposition); + ItemMouseLeave.SetAssociatedComposition(boundsComposition); + + callback = new ItemCallback(this); + itemProvider->AttachCallback(callback.Obj()); + axis = new GuiDefaultAxis; + + if (acceptFocus) + { + boundsComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiListControl::OnBoundsMouseButtonDown); + boundsComposition->GetEventReceiver()->middleButtonDown.AttachMethod(this, &GuiListControl::OnBoundsMouseButtonDown); + boundsComposition->GetEventReceiver()->rightButtonDown.AttachMethod(this, &GuiListControl::OnBoundsMouseButtonDown); + SetFocusableComposition(boundsComposition); + } + } + + GuiListControl::~GuiListControl() + { + if(itemArranger) + { + itemProvider->DetachCallback(itemArranger.Obj()); + } + callback->ClearCache(); + itemStyleProperty = {}; + itemArranger = nullptr; + } + + GuiListControl::IItemProvider* GuiListControl::GetItemProvider() + { + return itemProvider.Obj(); + } + + GuiListControl::ItemStyleProperty GuiListControl::GetItemTemplate() + { + return itemStyleProperty; + } + + void GuiListControl::SetItemTemplate(ItemStyleProperty value) + { + SetStyleAndArranger(value, itemArranger); + ItemTemplateChanged.Execute(GetNotifyEventArguments()); + } + + GuiListControl::IItemArranger* GuiListControl::GetArranger() + { + return itemArranger.Obj(); + } + + void GuiListControl::SetArranger(Ptr value) + { + SetStyleAndArranger(itemStyleProperty, value); + ArrangerChanged.Execute(GetNotifyEventArguments()); + } + + compositions::IGuiAxis* GuiListControl::GetAxis() + { + return axis.Obj(); + } + + void GuiListControl::SetAxis(Ptr value) + { + Ptr old = axis; + axis = value; + SetStyleAndArranger(itemStyleProperty, itemArranger); + AxisChanged.Execute(GetNotifyEventArguments()); + } + + bool GuiListControl::EnsureItemVisible(vint itemIndex) + { + if (itemIndex < 0 || itemIndex >= itemProvider->Count()) + { + return false; + } + return itemArranger ? itemArranger->EnsureItemVisible(itemIndex) : false; + } + + Size GuiListControl::GetAdoptedSize(Size expectedSize) + { + if (itemArranger) + { + Size controlSize = boundsComposition->GetBounds().GetSize(); + Size viewSize = containerComposition->GetBounds().GetSize(); + vint x = controlSize.x - viewSize.x; + vint y = controlSize.y - viewSize.y; + + Size expectedViewSize(expectedSize.x - x, expectedSize.y - y); + if (axis) + { + expectedViewSize = axis->RealSizeToVirtualSize(expectedViewSize); + } + Size adoptedViewSize = itemArranger->GetAdoptedSize(expectedViewSize); + if (axis) + { + adoptedViewSize = axis->VirtualSizeToRealSize(adoptedViewSize); + } + return Size(adoptedViewSize.x + x, adoptedViewSize.y + y); + } + return expectedSize; + } + + bool GuiListControl::GetDisplayItemBackground() + { + return displayItemBackground; + } + + void GuiListControl::SetDisplayItemBackground(bool value) + { + if (displayItemBackground != value) + { + displayItemBackground = value; + SetStyleAndArranger(itemStyleProperty, itemArranger); + } + } + +/*********************************************************************** +GuiSelectableListControl +***********************************************************************/ + + void GuiSelectableListControl::NotifySelectionChanged() + { + SelectionChanged.Execute(GetNotifyEventArguments()); + } + + void GuiSelectableListControl::OnItemModified(vint start, vint count, vint newCount) + { + GuiListControl::OnItemModified(start, count, newCount); + if(count!=newCount) + { + ClearSelection(); + } + } + + void GuiSelectableListControl::OnStyleInstalled(vint itemIndex, ItemStyle* style) + { + GuiListControl::OnStyleInstalled(itemIndex, style); + style->SetSelected(selectedItems.Contains(itemIndex)); + } + + void GuiSelectableListControl::OnItemSelectionChanged(vint itemIndex, bool value) + { + if(auto style = itemArranger->GetVisibleStyle(itemIndex)) + { + style->SetSelected(value); + } + } + + void GuiSelectableListControl::OnItemSelectionCleared() + { + FOREACH(ItemStyle*, style, visibleStyles.Keys()) + { + style->SetSelected(false); + } + } + + void GuiSelectableListControl::OnItemLeftButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiItemMouseEventArgs& arguments) + { + if(GetVisuallyEnabled()) + { + SelectItemsByClick(arguments.itemIndex, arguments.ctrl, arguments.shift, true); + } + } + + void GuiSelectableListControl::OnItemRightButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiItemMouseEventArgs& arguments) + { + if(GetVisuallyEnabled()) + { + SelectItemsByClick(arguments.itemIndex, arguments.ctrl, arguments.shift, false); + } + } + + void GuiSelectableListControl::NormalizeSelectedItemIndexStartEnd() + { + if(selectedItemIndexStart<0 || selectedItemIndexStart>=itemProvider->Count()) + { + selectedItemIndexStart=0; + } + if(selectedItemIndexEnd<0 || selectedItemIndexEnd>=itemProvider->Count()) + { + selectedItemIndexEnd=0; + } + } + + void GuiSelectableListControl::SetMultipleItemsSelectedSilently(vint start, vint end, bool selected) + { + if(start>end) + { + vint temp=start; + start=end; + end=temp; + } + vint count=itemProvider->Count(); + if(start<0) start=0; + if(end>=count) end=count-1; + for(vint i=start;i<=end;i++) + { + if(selected) + { + if(!selectedItems.Contains(i)) + { + selectedItems.Add(i); + } + } + else + { + selectedItems.Remove(i); + } + OnItemSelectionChanged(i, selected); + } + } + + void GuiSelectableListControl::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) + { + if(GetVisuallyEnabled()) + { + if(SelectItemsByKey(arguments.code, arguments.ctrl, arguments.shift)) + { + arguments.handled=true; + } + } + } + + GuiSelectableListControl::GuiSelectableListControl(theme::ThemeName themeName, IItemProvider* _itemProvider) + :GuiListControl(themeName, _itemProvider, true) + ,multiSelect(false) + ,selectedItemIndexStart(-1) + ,selectedItemIndexEnd(-1) + { + SelectionChanged.SetAssociatedComposition(boundsComposition); + ItemLeftButtonDown.AttachMethod(this, &GuiSelectableListControl::OnItemLeftButtonDown); + ItemRightButtonDown.AttachMethod(this, &GuiSelectableListControl::OnItemRightButtonDown); + if(focusableComposition) + { + focusableComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiSelectableListControl::OnKeyDown); + } + } + + GuiSelectableListControl::~GuiSelectableListControl() + { + } + + bool GuiSelectableListControl::GetMultiSelect() + { + return multiSelect; + } + + void GuiSelectableListControl::SetMultiSelect(bool value) + { + if (multiSelect != value) + { + multiSelect = value; + ClearSelection(); + } + } + + const collections::SortedList& GuiSelectableListControl::GetSelectedItems() + { + return selectedItems; + } + + vint GuiSelectableListControl::GetSelectedItemIndex() + { + return selectedItems.Count() == 1 ? selectedItems[0] : -1; + } + + WString GuiSelectableListControl::GetSelectedItemText() + { + vint index = GetSelectedItemIndex(); + if (index != -1) + { + return itemProvider->GetTextValue(index); + } + return L""; + } + + bool GuiSelectableListControl::GetSelected(vint itemIndex) + { + return selectedItems.Contains(itemIndex); + } + + void GuiSelectableListControl::SetSelected(vint itemIndex, bool value) + { + if(value) + { + if(!selectedItems.Contains(itemIndex)) + { + if(!multiSelect) + { + selectedItems.Clear(); + OnItemSelectionCleared(); + } + selectedItems.Add(itemIndex); + OnItemSelectionChanged(itemIndex, value); + NotifySelectionChanged(); + } + } + else + { + if(selectedItems.Remove(itemIndex)) + { + OnItemSelectionChanged(itemIndex, value); + NotifySelectionChanged(); + } + } + } + + bool GuiSelectableListControl::SelectItemsByClick(vint itemIndex, bool ctrl, bool shift, bool leftButton) + { + NormalizeSelectedItemIndexStartEnd(); + if(0<=itemIndex && itemIndexCount()) + { + if(!leftButton) + { + if(selectedItems.Contains(itemIndex)) + { + return true; + } + } + if(!multiSelect) + { + shift=false; + ctrl=false; + } + if(shift) + { + if(!ctrl) + { + SetMultipleItemsSelectedSilently(selectedItemIndexStart, selectedItemIndexEnd, false); + } + selectedItemIndexEnd=itemIndex; + SetMultipleItemsSelectedSilently(selectedItemIndexStart, selectedItemIndexEnd, true); + NotifySelectionChanged(); + } + else + { + if(ctrl) + { + vint index=selectedItems.IndexOf(itemIndex); + if(index==-1) + { + selectedItems.Add(itemIndex); + } + else + { + selectedItems.RemoveAt(index); + } + OnItemSelectionChanged(itemIndex, index==-1); + NotifySelectionChanged(); + } + else + { + selectedItems.Clear(); + OnItemSelectionCleared(); + selectedItems.Add(itemIndex); + OnItemSelectionChanged(itemIndex, true); + NotifySelectionChanged(); + } + selectedItemIndexStart=itemIndex; + selectedItemIndexEnd=itemIndex; + } + return true; + } + return false; + } + + bool GuiSelectableListControl::SelectItemsByKey(vint code, bool ctrl, bool shift) + { + if(!GetArranger()) return false; + + NormalizeSelectedItemIndexStartEnd(); + KeyDirection keyDirection=KeyDirection::Up; + switch(code) + { + case VKEY_UP: + keyDirection=KeyDirection::Up; + break; + case VKEY_DOWN: + keyDirection=KeyDirection::Down; + break; + case VKEY_LEFT: + keyDirection=KeyDirection::Left; + break; + case VKEY_RIGHT: + keyDirection=KeyDirection::Right; + break; + case VKEY_HOME: + keyDirection=KeyDirection::Home; + break; + case VKEY_END: + keyDirection=KeyDirection::End; + break; + case VKEY_PRIOR: + keyDirection=KeyDirection::PageUp; + break; + case VKEY_NEXT: + keyDirection=KeyDirection::PageDown; + break; + default: + return false; + } + + if(GetAxis()) + { + keyDirection=GetAxis()->RealKeyDirectionToVirtualKeyDirection(keyDirection); + } + vint itemIndex=GetArranger()->FindItem(selectedItemIndexEnd, keyDirection); + if(SelectItemsByClick(itemIndex, ctrl, shift, true)) + { + return EnsureItemVisible(itemIndex); + } + else + { + return false; + } + } + + void GuiSelectableListControl::ClearSelection() + { + if(selectedItems.Count()>0) + { + selectedItems.Clear(); + OnItemSelectionCleared(); + NotifySelectionChanged(); + } + } + + namespace list + { + +/*********************************************************************** +ItemProviderBase +***********************************************************************/ + + void ItemProviderBase::InvokeOnItemModified(vint start, vint count, vint newCount) + { + for (vint i = 0; i < callbacks.Count(); i++) + { + callbacks[i]->OnItemModified(start, count, newCount); + } + } + + ItemProviderBase::ItemProviderBase() + { + } + + ItemProviderBase::~ItemProviderBase() + { + for(vint i=0;iOnAttached(0); + } + } + + bool ItemProviderBase::AttachCallback(GuiListControl::IItemProviderCallback* value) + { + if(callbacks.Contains(value)) + { + return false; + } + else + { + callbacks.Add(value); + value->OnAttached(this); + return true; + } + } + + bool ItemProviderBase::DetachCallback(GuiListControl::IItemProviderCallback* value) + { + vint index=callbacks.IndexOf(value); + if(index==-1) + { + return false; + } + else + { + value->OnAttached(0); + callbacks.Remove(value); + return true; + } + } + + void ItemProviderBase::PushEditing() + { + editingCounter++; + } + + bool ItemProviderBase::PopEditing() + { + if (editingCounter == 0)return false; + editingCounter--; + return true; + } + + bool ItemProviderBase::IsEditing() + { + return editingCounter > 0; + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\LISTCONTROLPACKAGE\GUILISTVIEWCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace compositions; + using namespace collections; + using namespace reflection::description; + +/*********************************************************************** +GuiListViewColumnHeader +***********************************************************************/ + + void GuiListViewColumnHeader::BeforeControlTemplateUninstalled_() + { + } + + void GuiListViewColumnHeader::AfterControlTemplateInstalled_(bool initialize) + { + GetControlTemplateObject(true)->SetSortingState(columnSortingState); + } + + GuiListViewColumnHeader::GuiListViewColumnHeader(theme::ThemeName themeName) + :GuiMenuButton(themeName) + { + } + + GuiListViewColumnHeader::~GuiListViewColumnHeader() + { + } + + bool GuiListViewColumnHeader::IsAltAvailable() + { + return false; + } + + ColumnSortingState GuiListViewColumnHeader::GetColumnSortingState() + { + return columnSortingState; + } + + void GuiListViewColumnHeader::SetColumnSortingState(ColumnSortingState value) + { + if (columnSortingState != value) + { + columnSortingState = value; + GetControlTemplateObject(true)->SetSortingState(columnSortingState); + } + } + +/*********************************************************************** +GuiListViewBase +***********************************************************************/ + + void GuiListViewBase::BeforeControlTemplateUninstalled_() + { + } + + void GuiListViewBase::AfterControlTemplateInstalled_(bool initialize) + { + } + + GuiListViewBase::GuiListViewBase(theme::ThemeName themeName, GuiListControl::IItemProvider* _itemProvider) + :GuiSelectableListControl(themeName, _itemProvider) + { + ColumnClicked.SetAssociatedComposition(boundsComposition); + } + + GuiListViewBase::~GuiListViewBase() + { + } + + namespace list + { + + const wchar_t* const IListViewItemView::Identifier = L"vl::presentation::controls::list::IListViewItemView"; + +/*********************************************************************** +ListViewColumnItemArranger::ColumnItemViewCallback +***********************************************************************/ + + ListViewColumnItemArranger::ColumnItemViewCallback::ColumnItemViewCallback(ListViewColumnItemArranger* _arranger) + :arranger(_arranger) + { + } + + ListViewColumnItemArranger::ColumnItemViewCallback::~ColumnItemViewCallback() + { + } + + void ListViewColumnItemArranger::ColumnItemViewCallback::OnColumnChanged() + { + arranger->RebuildColumns(); + FOREACH(ItemStyleRecord, style, arranger->visibleStyles) + { + if (auto callback = dynamic_cast(style.key)) + { + callback->OnColumnChanged(); + } + } + } + +/*********************************************************************** +ListViewColumnItemArranger +***********************************************************************/ + + const wchar_t* const ListViewColumnItemArranger::IColumnItemView::Identifier = L"vl::presentation::controls::list::ListViewColumnItemArranger::IColumnItemView"; + + void ListViewColumnItemArranger::ColumnClicked(vint index, compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + GuiItemEventArgs args(listView->ColumnClicked.GetAssociatedComposition()); + args.itemIndex=index; + listView->ColumnClicked.Execute(args); + } + + void ListViewColumnItemArranger::ColumnBoundsChanged(vint index, compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + GuiBoundsComposition* buttonBounds=columnHeaderButtons[index]->GetBoundsComposition(); + vint size=buttonBounds->GetBounds().Width(); + if(size>columnItemView->GetColumnSize(index)) + { + columnItemView->SetColumnSize(index, size); + } + } + + void ListViewColumnItemArranger::ColumnHeaderSplitterLeftButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(listView->GetVisuallyEnabled()) + { + arguments.handled=true; + splitterDragging=true; + splitterLatestX=arguments.x; + } + } + + void ListViewColumnItemArranger::ColumnHeaderSplitterLeftButtonUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(listView->GetVisuallyEnabled()) + { + arguments.handled=true; + splitterDragging=false; + splitterLatestX=0; + } + } + + void ListViewColumnItemArranger::ColumnHeaderSplitterMouseMove(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(splitterDragging) + { + vint offset=arguments.x-splitterLatestX; + vint index=columnHeaderSplitters.IndexOf(dynamic_cast(sender)); + if(index!=-1) + { + GuiBoundsComposition* buttonBounds=columnHeaderButtons[index]->GetBoundsComposition(); + Rect bounds=buttonBounds->GetBounds(); + Rect newBounds(bounds.LeftTop(), Size(bounds.Width()+offset, bounds.Height())); + buttonBounds->SetBounds(newBounds); + + vint finalSize=buttonBounds->GetBounds().Width(); + columnItemView->SetColumnSize(index, finalSize); + } + } + } + + void ListViewColumnItemArranger::RearrangeItemBounds() + { + FixedHeightItemArranger::RearrangeItemBounds(); + vint count = columnHeaders->GetParent()->Children().Count(); + columnHeaders->GetParent()->MoveChild(columnHeaders, count - 1); + columnHeaders->SetBounds(Rect(Point(-viewBounds.Left(), 0), Size(0, 0))); + } + + vint ListViewColumnItemArranger::GetWidth() + { + vint width=columnHeaders->GetBounds().Width()-SplitterWidth; + if(widthGetBounds().Height(); + } + + Size ListViewColumnItemArranger::OnCalculateTotalSize() + { + Size size=FixedHeightItemArranger::OnCalculateTotalSize(); + size.x+=SplitterWidth; + return size; + } + + void ListViewColumnItemArranger::DeleteColumnButtons() + { + for(vint i=columnHeaders->GetStackItems().Count()-1;i>=0;i--) + { + GuiStackItemComposition* item=columnHeaders->GetStackItems().Get(i); + columnHeaders->RemoveChild(item); + + GuiControl* button=item->Children().Get(0)->GetAssociatedControl(); + if(button) + { + item->RemoveChild(button->GetBoundsComposition()); + delete button; + } + delete item; + } + columnHeaderButtons.Clear(); + columnHeaderSplitters.Clear(); + } + + void ListViewColumnItemArranger::RebuildColumns() + { + if (columnItemView && columnHeaderButtons.Count() == listViewItemView->GetColumnCount()) + { + for (vint i = 0; i < listViewItemView->GetColumnCount(); i++) + { + GuiListViewColumnHeader* button = columnHeaderButtons[i]; + button->SetText(listViewItemView->GetColumnText(i)); + button->SetSubMenu(columnItemView->GetDropdownPopup(i), false); + button->SetColumnSortingState(columnItemView->GetSortingState(i)); + button->GetBoundsComposition()->SetBounds(Rect(Point(0, 0), Size(columnItemView->GetColumnSize(i), 0))); + } + } + else + { + DeleteColumnButtons(); + if (columnItemView && listViewItemView) + { + for (vint i = 0; i < listViewItemView->GetColumnCount(); i++) + { + GuiBoundsComposition* splitterComposition = new GuiBoundsComposition; + splitterComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + splitterComposition->SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::SizeWE)); + splitterComposition->SetAlignmentToParent(Margin(0, 0, -1, 0)); + splitterComposition->SetPreferredMinSize(Size(SplitterWidth, 0)); + columnHeaderSplitters.Add(splitterComposition); + + splitterComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &ListViewColumnItemArranger::ColumnHeaderSplitterLeftButtonDown); + splitterComposition->GetEventReceiver()->leftButtonUp.AttachMethod(this, &ListViewColumnItemArranger::ColumnHeaderSplitterLeftButtonUp); + splitterComposition->GetEventReceiver()->mouseMove.AttachMethod(this, &ListViewColumnItemArranger::ColumnHeaderSplitterMouseMove); + } + for (vint i = 0; i < listViewItemView->GetColumnCount(); i++) + { + GuiListViewColumnHeader* button = new GuiListViewColumnHeader(theme::ThemeName::Unknown); + button->SetControlTemplate(listView->GetControlTemplateObject(true)->GetColumnHeaderTemplate()); + button->SetText(listViewItemView->GetColumnText(i)); + button->SetSubMenu(columnItemView->GetDropdownPopup(i), false); + button->SetColumnSortingState(columnItemView->GetSortingState(i)); + button->GetBoundsComposition()->SetBounds(Rect(Point(0, 0), Size(columnItemView->GetColumnSize(i), 0))); + button->Clicked.AttachLambda(Curry(Func(this, &ListViewColumnItemArranger::ColumnClicked))(i)); + button->GetBoundsComposition()->BoundsChanged.AttachLambda(Curry(Func(this, &ListViewColumnItemArranger::ColumnBoundsChanged))(i)); + columnHeaderButtons.Add(button); + if (i > 0) + { + button->GetContainerComposition()->AddChild(columnHeaderSplitters[i - 1]); + } + + GuiStackItemComposition* item = new GuiStackItemComposition; + item->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + item->AddChild(button->GetBoundsComposition()); + columnHeaders->AddChild(item); + } + if (listViewItemView->GetColumnCount() > 0) + { + GuiBoundsComposition* splitterComposition = columnHeaderSplitters[listViewItemView->GetColumnCount() - 1]; + + GuiStackItemComposition* item = new GuiStackItemComposition; + item->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + item->AddChild(splitterComposition); + columnHeaders->AddChild(item); + } + } + } + callback->OnTotalSizeChanged(); + } + + ListViewColumnItemArranger::ListViewColumnItemArranger() + { + columnHeaders = new GuiStackComposition; + columnHeaders->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + columnItemViewCallback = new ColumnItemViewCallback(this); + } + + ListViewColumnItemArranger::~ListViewColumnItemArranger() + { + if(!columnHeaders->GetParent()) + { + DeleteColumnButtons(); + delete columnHeaders; + } + } + + void ListViewColumnItemArranger::AttachListControl(GuiListControl* value) + { + FixedHeightItemArranger::AttachListControl(value); + listView = dynamic_cast(value); + if (listView) + { + listViewItemView = dynamic_cast(listView->GetItemProvider()->RequestView(IListViewItemView::Identifier)); + columnItemView = dynamic_cast(listView->GetItemProvider()->RequestView(IColumnItemView::Identifier)); + listView->GetContainerComposition()->AddChild(columnHeaders); + if (columnItemView) + { + columnItemView->AttachCallback(columnItemViewCallback.Obj()); + RebuildColumns(); + } + } + } + + void ListViewColumnItemArranger::DetachListControl() + { + if (listView) + { + if (columnItemView) + { + columnItemView->DetachCallback(columnItemViewCallback.Obj()); + columnItemView = nullptr; + } + listViewItemView = nullptr; + listView->GetContainerComposition()->RemoveChild(columnHeaders); + listView = nullptr; + } + FixedHeightItemArranger::DetachListControl(); + } + +/*********************************************************************** +ListViewSubItems +***********************************************************************/ + + void ListViewSubItems::NotifyUpdateInternal(vint start, vint count, vint newCount) + { + owner->NotifyUpdate(); + } + +/*********************************************************************** +ListViewItem +***********************************************************************/ + + void ListViewItem::NotifyUpdate() + { + if (owner) + { + vint index = owner->IndexOf(this); + owner->NotifyUpdateInternal(index, 1, 1); + } + } + + ListViewItem::ListViewItem() + :owner(0) + { + subItems.owner = this; + } + + ListViewSubItems& ListViewItem::GetSubItems() + { + return subItems; + } + + Ptr ListViewItem::GetSmallImage() + { + return smallImage; + } + + void ListViewItem::SetSmallImage(Ptr value) + { + smallImage = value; + NotifyUpdate(); + } + + Ptr ListViewItem::GetLargeImage() + { + return largeImage; + } + + void ListViewItem::SetLargeImage(Ptr value) + { + largeImage = value; + NotifyUpdate(); + } + + const WString& ListViewItem::GetText() + { + return text; + } + + void ListViewItem::SetText(const WString& value) + { + text = value; + NotifyUpdate(); + } + + description::Value ListViewItem::GetTag() + { + return tag; + } + + void ListViewItem::SetTag(const description::Value& value) + { + tag = value; + NotifyUpdate(); + } + +/*********************************************************************** +ListViewColumn +***********************************************************************/ + + void ListViewColumn::NotifyUpdate(bool affectItem) + { + if (owner) + { + vint index = owner->IndexOf(this); + owner->NotifyColumnUpdated(index, affectItem); + } + } + + ListViewColumn::ListViewColumn(const WString& _text, vint _size) + :text(_text) + ,size(_size) + { + } + + ListViewColumn::~ListViewColumn() + { + if (dropdownPopup && ownPopup) + { + SafeDeleteControl(dropdownPopup); + } + } + + const WString& ListViewColumn::GetText() + { + return text; + } + + void ListViewColumn::SetText(const WString& value) + { + if (text != value) + { + text = value; + NotifyUpdate(false); + } + } + + ItemProperty ListViewColumn::GetTextProperty() + { + return textProperty; + } + + void ListViewColumn::SetTextProperty(const ItemProperty& value) + { + textProperty = value; + NotifyUpdate(true); + } + + vint ListViewColumn::GetSize() + { + return size; + } + + void ListViewColumn::SetSize(vint value) + { + if (size != value) + { + size = value; + NotifyUpdate(false); + } + } + + bool ListViewColumn::GetOwnPopup() + { + return ownPopup; + } + + void ListViewColumn::SetOwnPopup(bool value) + { + ownPopup = value; + } + + GuiMenu* ListViewColumn::GetDropdownPopup() + { + return dropdownPopup; + } + + void ListViewColumn::SetDropdownPopup(GuiMenu* value) + { + if (dropdownPopup != value) + { + dropdownPopup = value; + NotifyUpdate(false); + } + } + + ColumnSortingState ListViewColumn::GetSortingState() + { + return sortingState; + } + + void ListViewColumn::SetSortingState(ColumnSortingState value) + { + if (sortingState != value) + { + sortingState = value; + NotifyUpdate(false); + } + } + +/*********************************************************************** +ListViewDataColumns +***********************************************************************/ + + void ListViewDataColumns::NotifyUpdateInternal(vint start, vint count, vint newCount) + { + itemProvider->NotifyAllItemsUpdate(); + } + + ListViewDataColumns::ListViewDataColumns(IListViewItemProvider* _itemProvider) + :itemProvider(_itemProvider) + { + } + + ListViewDataColumns::~ListViewDataColumns() + { + } + +/*********************************************************************** +ListViewColumns +***********************************************************************/ + + void ListViewColumns::NotifyColumnUpdated(vint column, bool affectItem) + { + affectItemFlag = affectItem; + NotifyUpdate(column, 1); + affectItemFlag = true; + } + + void ListViewColumns::AfterInsert(vint index, const Ptr& value) + { + collections::ObservableListBase>::AfterInsert(index, value); + value->owner = this; + } + + void ListViewColumns::BeforeRemove(vint index, const Ptr& value) + { + value->owner = 0; + collections::ObservableListBase>::BeforeRemove(index, value); + } + + void ListViewColumns::NotifyUpdateInternal(vint start, vint count, vint newCount) + { + itemProvider->NotifyAllColumnsUpdate(); + if (affectItemFlag) + { + itemProvider->NotifyAllItemsUpdate(); + } + } + + ListViewColumns::ListViewColumns(IListViewItemProvider* _itemProvider) + :itemProvider(_itemProvider) + { + } + + ListViewColumns::~ListViewColumns() + { + } + +/*********************************************************************** +ListViewItemProvider +***********************************************************************/ + + void ListViewItemProvider::AfterInsert(vint index, const Ptr& value) + { + ListProvider>::AfterInsert(index, value); + value->owner = this; + } + + void ListViewItemProvider::BeforeRemove(vint index, const Ptr& value) + { + value->owner = 0; + ListProvider>::AfterInsert(index, value); + } + + void ListViewItemProvider::NotifyAllItemsUpdate() + { + NotifyUpdate(0, Count()); + } + + void ListViewItemProvider::NotifyAllColumnsUpdate() + { + for (vint i = 0; i < columnItemViewCallbacks.Count(); i++) + { + columnItemViewCallbacks[i]->OnColumnChanged(); + } + } + + Ptr ListViewItemProvider::GetSmallImage(vint itemIndex) + { + return Get(itemIndex)->smallImage; + } + + Ptr ListViewItemProvider::GetLargeImage(vint itemIndex) + { + return Get(itemIndex)->largeImage; + } + + WString ListViewItemProvider::GetText(vint itemIndex) + { + return Get(itemIndex)->text; + } + + WString ListViewItemProvider::GetSubItem(vint itemIndex, vint index) + { + Ptr item=Get(itemIndex); + if(index<0 || index>=item->GetSubItems().Count()) + { + return L""; + } + else + { + return item->GetSubItems()[index]; + } + } + + vint ListViewItemProvider::GetDataColumnCount() + { + return dataColumns.Count(); + } + + vint ListViewItemProvider::GetDataColumn(vint index) + { + return dataColumns[index]; + } + + vint ListViewItemProvider::GetColumnCount() + { + return columns.Count(); + } + + WString ListViewItemProvider::GetColumnText(vint index) + { + if (index<0 || index >= columns.Count()) + { + return L""; + } + else + { + return columns[index]->GetText(); + } + } + + bool ListViewItemProvider::AttachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) + { + if(columnItemViewCallbacks.Contains(value)) + { + return false; + } + else + { + columnItemViewCallbacks.Add(value); + return true; + } + } + + bool ListViewItemProvider::DetachCallback(ListViewColumnItemArranger::IColumnItemViewCallback* value) + { + vint index=columnItemViewCallbacks.IndexOf(value); + if(index==-1) + { + return false; + } + else + { + columnItemViewCallbacks.Remove(value); + return true; + } + } + + vint ListViewItemProvider::GetColumnSize(vint index) + { + if(index<0 || index>=columns.Count()) + { + return 0; + } + else + { + return columns[index]->GetSize(); + } + } + + void ListViewItemProvider::SetColumnSize(vint index, vint value) + { + if(index>=0 && indexSetSize(value); + } + } + + GuiMenu* ListViewItemProvider::GetDropdownPopup(vint index) + { + if(index<0 || index>=columns.Count()) + { + return 0; + } + else + { + return columns[index]->GetDropdownPopup(); + } + } + + ColumnSortingState ListViewItemProvider::GetSortingState(vint index) + { + if (index < 0 || index >= columns.Count()) + { + return ColumnSortingState::NotSorted; + } + else + { + return columns[index]->GetSortingState(); + } + } + + WString ListViewItemProvider::GetTextValue(vint itemIndex) + { + return GetText(itemIndex); + } + + description::Value ListViewItemProvider::GetBindingValue(vint itemIndex) + { + return Value::From(Get(itemIndex)); + } + + ListViewItemProvider::ListViewItemProvider() + :columns(this) + , dataColumns(this) + { + } + + ListViewItemProvider::~ListViewItemProvider() + { + } + + IDescriptable* ListViewItemProvider::RequestView(const WString& identifier) + { + if (identifier == IListViewItemView::Identifier) + { + return (IListViewItemView*)this; + } + else if (identifier == ListViewColumnItemArranger::IColumnItemView::Identifier) + { + return (ListViewColumnItemArranger::IColumnItemView*)this; + } + else + { + return 0; + } + } + + ListViewDataColumns& ListViewItemProvider::GetDataColumns() + { + return dataColumns; + } + + ListViewColumns& ListViewItemProvider::GetColumns() + { + return columns; + } + } + +/*********************************************************************** +GuiListView +***********************************************************************/ + + void GuiVirtualListView::OnStyleInstalled(vint itemIndex, ItemStyle* style) + { + GuiListViewBase::OnStyleInstalled(itemIndex, style); + } + + void GuiVirtualListView::OnItemTemplateChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + view = ListViewView::Unknown; + } + + GuiVirtualListView::GuiVirtualListView(theme::ThemeName themeName, GuiListControl::IItemProvider* _itemProvider) + :GuiListViewBase(themeName, _itemProvider) + { + SetView(ListViewView::Detail); + } + + GuiVirtualListView::~GuiVirtualListView() + { + } + + ListViewView GuiVirtualListView::GetView() + { + return view; + } + + void GuiVirtualListView::SetView(ListViewView _view) + { + switch (_view) + { + case ListViewView::BigIcon: + SetStyleAndArranger( + [](const Value&) { return new list::BigIconListViewItemTemplate; }, + new list::FixedSizeMultiColumnItemArranger + ); + break; + case ListViewView::SmallIcon: + SetStyleAndArranger( + [](const Value&) { return new list::SmallIconListViewItemTemplate; }, + new list::FixedSizeMultiColumnItemArranger + ); + break; + case ListViewView::List: + SetStyleAndArranger( + [](const Value&) { return new list::ListListViewItemTemplate; }, + new list::FixedHeightMultiColumnItemArranger + ); + break; + case ListViewView::Tile: + SetStyleAndArranger( + [](const Value&) { return new list::TileListViewItemTemplate; }, + new list::FixedSizeMultiColumnItemArranger + ); + break; + case ListViewView::Information: + SetStyleAndArranger( + [](const Value&) { return new list::InformationListViewItemTemplate; }, + new list::FixedHeightItemArranger + ); + break; + case ListViewView::Detail: + SetStyleAndArranger( + [](const Value&) { return new list::DetailListViewItemTemplate; }, + new list::ListViewColumnItemArranger + ); + break; + default:; + } + view = _view; + } + +/*********************************************************************** +GuiListView +***********************************************************************/ + + GuiListView::GuiListView(theme::ThemeName themeName) + :GuiVirtualListView(themeName, new list::ListViewItemProvider) + { + items=dynamic_cast(itemProvider.Obj()); + } + + GuiListView::~GuiListView() + { + } + + list::ListViewItemProvider& GuiListView::GetItems() + { + return *items; + } + + list::ListViewDataColumns& GuiListView::GetDataColumns() + { + return items->GetDataColumns(); + } + + list::ListViewColumns& GuiListView::GetColumns() + { + return items->GetColumns(); + } + + Ptr GuiListView::GetSelectedItem() + { + vint index = GetSelectedItemIndex(); + if (index == -1) return 0; + return items->Get(index); + } + } + } +} + +/*********************************************************************** +.\CONTROLS\LISTCONTROLPACKAGE\GUILISTVIEWITEMTEMPLATES.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace compositions; + using namespace collections; + using namespace reflection::description; + + namespace list + { + +/*********************************************************************** +DefaultListViewItemTemplate +***********************************************************************/ + + DefaultListViewItemTemplate::DefaultListViewItemTemplate() + { + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + } + + DefaultListViewItemTemplate::~DefaultListViewItemTemplate() + { + } + +/*********************************************************************** +BigIconListViewItemTemplate +***********************************************************************/ + + void BigIconListViewItemTemplate::OnInitialize() + { + DefaultListViewItemTemplate::OnInitialize(); + { + auto table = new GuiTableComposition; + AddChild(table); + table->SetRowsAndColumns(2, 3); + table->SetRowOption(0, GuiCellOption::MinSizeOption()); + table->SetRowOption(1, GuiCellOption::MinSizeOption()); + table->SetColumnOption(0, GuiCellOption::PercentageOption(0.5)); + table->SetColumnOption(1, GuiCellOption::MinSizeOption()); + table->SetColumnOption(2, GuiCellOption::PercentageOption(0.5)); + table->SetAlignmentToParent(Margin(0, 0, 0, 0)); + table->SetCellPadding(5); + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 1, 1, 1); + cell->SetPreferredMinSize(Size(32, 32)); + + image = GuiImageFrameElement::Create(); + image->SetStretch(true); + cell->SetOwnedElement(image); + } + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetMinSizeLimitation(GuiGraphicsComposition::NoLimit); + cell->SetSite(1, 0, 1, 3); + cell->SetPreferredMinSize(Size(64, 40)); + + text = GuiSolidLabelElement::Create(); + text->SetAlignments(Alignment::Center, Alignment::Top); + text->SetWrapLine(true); + text->SetEllipse(true); + cell->SetOwnedElement(text); + } + } + + if (auto listView = dynamic_cast(listControl)) + { + auto itemIndex = GetIndex(); + if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + auto imageData = view->GetLargeImage(itemIndex); + if (imageData) + { + image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); + } + else + { + image->SetImage(nullptr); + } + text->SetText(view->GetText(itemIndex)); + text->SetColor(listView->GetControlTemplateObject(true)->GetPrimaryTextColor()); + } + } + + FontChanged.AttachMethod(this, &BigIconListViewItemTemplate::OnFontChanged); + + FontChanged.Execute(compositions::GuiEventArgs(this)); + } + + void BigIconListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetFont(GetFont()); + } + + BigIconListViewItemTemplate::BigIconListViewItemTemplate() + { + } + + BigIconListViewItemTemplate::~BigIconListViewItemTemplate() + { + } + +/*********************************************************************** +SmallIconListViewItemTemplate +***********************************************************************/ + + void SmallIconListViewItemTemplate::OnInitialize() + { + DefaultListViewItemTemplate::OnInitialize(); + { + auto table = new GuiTableComposition; + AddChild(table); + table->SetRowsAndColumns(3, 2); + table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); + table->SetRowOption(1, GuiCellOption::MinSizeOption()); + table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); + table->SetColumnOption(0, GuiCellOption::MinSizeOption()); + table->SetColumnOption(1, GuiCellOption::MinSizeOption()); + table->SetAlignmentToParent(Margin(0, 0, 0, 0)); + table->SetCellPadding(2); + { + GuiCellComposition* cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(1, 0, 1, 1); + cell->SetPreferredMinSize(Size(16, 16)); + + image = GuiImageFrameElement::Create(); + image->SetStretch(true); + cell->SetOwnedElement(image); + } + { + GuiCellComposition* cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 1, 3, 1); + cell->SetPreferredMinSize(Size(192, 0)); + + text = GuiSolidLabelElement::Create(); + text->SetAlignments(Alignment::Left, Alignment::Center); + text->SetEllipse(true); + cell->SetOwnedElement(text); + } + } + + if (auto listView = dynamic_cast(listControl)) + { + auto itemIndex = GetIndex(); + if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + auto imageData = view->GetSmallImage(itemIndex); + if (imageData) + { + image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); + } + else + { + image->SetImage(nullptr); + } + text->SetText(view->GetText(itemIndex)); + text->SetColor(listView->GetControlTemplateObject(true)->GetPrimaryTextColor()); + } + } + + FontChanged.AttachMethod(this, &SmallIconListViewItemTemplate::OnFontChanged); + + FontChanged.Execute(compositions::GuiEventArgs(this)); + } + + void SmallIconListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetFont(GetFont()); + } + + SmallIconListViewItemTemplate::SmallIconListViewItemTemplate() + { + } + + SmallIconListViewItemTemplate::~SmallIconListViewItemTemplate() + { + } + +/*********************************************************************** +ListListViewItemTemplate +***********************************************************************/ + + void ListListViewItemTemplate::OnInitialize() + { + DefaultListViewItemTemplate::OnInitialize(); + { + auto table = new GuiTableComposition; + AddChild(table); + table->SetRowsAndColumns(3, 2); + table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); + table->SetRowOption(1, GuiCellOption::MinSizeOption()); + table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); + table->SetColumnOption(0, GuiCellOption::MinSizeOption()); + table->SetColumnOption(1, GuiCellOption::MinSizeOption()); + table->SetAlignmentToParent(Margin(0, 0, 0, 0)); + table->SetCellPadding(2); + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(1, 0, 1, 1); + cell->SetPreferredMinSize(Size(16, 16)); + + image = GuiImageFrameElement::Create(); + image->SetStretch(true); + cell->SetOwnedElement(image); + } + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 1, 3, 1); + cell->SetMargin(Margin(0, 0, 16, 0)); + + text = GuiSolidLabelElement::Create(); + text->SetAlignments(Alignment::Left, Alignment::Center); + cell->SetOwnedElement(text); + } + } + + if (auto listView = dynamic_cast(listControl)) + { + auto itemIndex = GetIndex(); + if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + auto imageData = view->GetSmallImage(itemIndex); + if (imageData) + { + image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); + } + else + { + image->SetImage(nullptr); + } + text->SetText(view->GetText(itemIndex)); + text->SetColor(listView->GetControlTemplateObject(true)->GetPrimaryTextColor()); + } + } + + FontChanged.AttachMethod(this, &ListListViewItemTemplate::OnFontChanged); + + FontChanged.Execute(compositions::GuiEventArgs(this)); + } + + void ListListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetFont(GetFont()); + } + + ListListViewItemTemplate::ListListViewItemTemplate() + { + } + + ListListViewItemTemplate::~ListListViewItemTemplate() + { + } + +/*********************************************************************** +TileListViewItemTemplate +***********************************************************************/ + + elements::GuiSolidLabelElement* TileListViewItemTemplate::CreateTextElement(vint textRow) + { + auto cell = new GuiCellComposition; + textTable->AddChild(cell); + cell->SetSite(textRow + 1, 0, 1, 1); + + auto textElement = GuiSolidLabelElement::Create(); + textElement->SetAlignments(Alignment::Left, Alignment::Center); + textElement->SetEllipse(true); + cell->SetOwnedElement(textElement); + return textElement; + } + + void TileListViewItemTemplate::ResetTextTable(vint textRows) + { + textTable->SetRowsAndColumns(textRows + 2, 1); + textTable->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); + for (vint i = 0; iSetRowOption(i + 1, GuiCellOption::MinSizeOption()); + } + textTable->SetRowOption(textRows + 1, GuiCellOption::PercentageOption(0.5)); + textTable->SetColumnOption(0, GuiCellOption::PercentageOption(1.0)); + } + + void TileListViewItemTemplate::OnInitialize() + { + DefaultListViewItemTemplate::OnInitialize(); + { + auto table = new GuiTableComposition; + AddChild(table); + table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + table->SetRowsAndColumns(3, 2); + table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); + table->SetRowOption(1, GuiCellOption::MinSizeOption()); + table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); + table->SetColumnOption(0, GuiCellOption::MinSizeOption()); + table->SetColumnOption(1, GuiCellOption::MinSizeOption()); + table->SetAlignmentToParent(Margin(0, 0, 0, 0)); + table->SetCellPadding(4); + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(1, 0, 1, 1); + cell->SetPreferredMinSize(Size(32, 32)); + + image = GuiImageFrameElement::Create(); + image->SetStretch(true); + cell->SetOwnedElement(image); + } + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 1, 3, 1); + cell->SetPreferredMinSize(Size(224, 0)); + + textTable = new GuiTableComposition; + textTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + textTable->SetCellPadding(1); + ResetTextTable(1); + textTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); + cell->AddChild(textTable); + { + text = CreateTextElement(0); + } + } + } + + if (auto listView = dynamic_cast(listControl)) + { + auto itemIndex = GetIndex(); + if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + auto imageData = view->GetLargeImage(itemIndex); + if (imageData) + { + image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); + } + else + { + image->SetImage(nullptr); + } + text->SetText(view->GetText(itemIndex)); + text->SetColor(listView->GetControlTemplateObject(true)->GetPrimaryTextColor()); + + vint dataColumnCount = view->GetDataColumnCount(); + ResetTextTable(dataColumnCount + 1); + dataTexts.Resize(dataColumnCount); + for (vint i = 0; i < dataColumnCount; i++) + { + dataTexts[i] = CreateTextElement(i + 1); + dataTexts[i]->SetText(view->GetSubItem(itemIndex, view->GetDataColumn(i))); + dataTexts[i]->SetColor(listView->GetControlTemplateObject(true)->GetSecondaryTextColor()); + } + } + } + + FontChanged.AttachMethod(this, &TileListViewItemTemplate::OnFontChanged); + + FontChanged.Execute(compositions::GuiEventArgs(this)); + } + + void TileListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetFont(GetFont()); + if (auto view = dynamic_cast(listControl->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + vint dataColumnCount = view->GetDataColumnCount(); + for (vint i = 0; i < dataColumnCount; i++) + { + dataTexts[i]->SetFont(GetFont()); + } + } + } + + TileListViewItemTemplate::TileListViewItemTemplate() + { + } + + TileListViewItemTemplate::~TileListViewItemTemplate() + { + } + +/*********************************************************************** +InformationListViewItemTemplate +***********************************************************************/ + + void InformationListViewItemTemplate::OnInitialize() + { + DefaultListViewItemTemplate::OnInitialize(); + { + bottomLine = GuiSolidBackgroundElement::Create(); + bottomLineComposition = new GuiBoundsComposition; + bottomLineComposition->SetOwnedElement(bottomLine); + bottomLineComposition->SetAlignmentToParent(Margin(8, -1, 8, 0)); + bottomLineComposition->SetPreferredMinSize(Size(0, 1)); + AddChild(bottomLineComposition); + + auto table = new GuiTableComposition; + AddChild(table); + table->SetRowsAndColumns(3, 3); + table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); + table->SetRowOption(1, GuiCellOption::MinSizeOption()); + table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); + table->SetColumnOption(0, GuiCellOption::MinSizeOption()); + table->SetColumnOption(1, GuiCellOption::PercentageOption(1.0)); + table->SetColumnOption(2, GuiCellOption::MinSizeOption()); + table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + table->SetAlignmentToParent(Margin(0, 0, 0, 0)); + table->SetCellPadding(4); + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(1, 0, 1, 1); + cell->SetPreferredMinSize(Size(32, 32)); + + image = GuiImageFrameElement::Create(); + image->SetStretch(true); + cell->SetOwnedElement(image); + } + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 1, 3, 1); + + text = GuiSolidLabelElement::Create(); + text->SetEllipse(true); + cell->SetOwnedElement(text); + } + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 2, 3, 1); + cell->SetPreferredMinSize(Size(224, 0)); + + textTable = new GuiTableComposition; + textTable->SetCellPadding(4); + textTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + textTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); + cell->AddChild(textTable); + } + } + + if (auto listView = dynamic_cast(listControl)) + { + auto itemIndex = GetIndex(); + if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + auto imageData = view->GetLargeImage(itemIndex); + if (imageData) + { + image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); + } + else + { + image->SetImage(nullptr); + } + text->SetText(view->GetText(itemIndex)); + text->SetColor(listView->GetControlTemplateObject(true)->GetPrimaryTextColor()); + bottomLine->SetColor(listView->GetControlTemplateObject(true)->GetItemSeparatorColor()); + + vint dataColumnCount = view->GetDataColumnCount(); + columnTexts.Resize(dataColumnCount); + dataTexts.Resize(dataColumnCount); + + textTable->SetRowsAndColumns(dataColumnCount + 2, 1); + textTable->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); + for (vint i = 0; i < dataColumnCount; i++) + { + textTable->SetRowOption(i + 1, GuiCellOption::MinSizeOption()); + } + textTable->SetRowOption(dataColumnCount + 1, GuiCellOption::PercentageOption(0.5)); + textTable->SetColumnOption(0, GuiCellOption::PercentageOption(1.0)); + + for (vint i = 0; i < dataColumnCount; i++) + { + auto cell = new GuiCellComposition; + textTable->AddChild(cell); + cell->SetSite(i + 1, 0, 1, 1); + + auto dataTable = new GuiTableComposition; + dataTable->SetRowsAndColumns(1, 2); + dataTable->SetRowOption(0, GuiCellOption::MinSizeOption()); + dataTable->SetColumnOption(0, GuiCellOption::MinSizeOption()); + dataTable->SetColumnOption(1, GuiCellOption::PercentageOption(1.0)); + dataTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + dataTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); + cell->AddChild(dataTable); + { + auto cell = new GuiCellComposition; + dataTable->AddChild(cell); + cell->SetSite(0, 0, 1, 1); + + columnTexts[i] = GuiSolidLabelElement::Create(); + columnTexts[i]->SetText(view->GetColumnText(view->GetDataColumn(i) + 1) + L": "); + columnTexts[i]->SetColor(listView->GetControlTemplateObject(true)->GetSecondaryTextColor()); + cell->SetOwnedElement(columnTexts[i]); + } + { + auto cell = new GuiCellComposition; + dataTable->AddChild(cell); + cell->SetSite(0, 1, 1, 1); + + dataTexts[i]= GuiSolidLabelElement::Create(); + dataTexts[i]->SetEllipse(true); + dataTexts[i]->SetText(view->GetSubItem(itemIndex, view->GetDataColumn(i))); + dataTexts[i]->SetColor(listView->GetControlTemplateObject(true)->GetPrimaryTextColor()); + cell->SetOwnedElement(dataTexts[i]); + } + } + } + } + + FontChanged.AttachMethod(this, &InformationListViewItemTemplate::OnFontChanged); + + FontChanged.Execute(compositions::GuiEventArgs(this)); + } + + void InformationListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + { + auto font = GetFont(); + font.size = (vint)(font.size * 1.2); + text->SetFont(font); + } + if (auto view = dynamic_cast(listControl->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + vint dataColumnCount = view->GetDataColumnCount(); + for (vint i = 0; i < dataColumnCount; i++) + { + columnTexts[i]->SetFont(GetFont()); + dataTexts[i]->SetFont(GetFont()); + } + } + } + + InformationListViewItemTemplate::InformationListViewItemTemplate() + { + } + + InformationListViewItemTemplate::~InformationListViewItemTemplate() + { + } + +/*********************************************************************** +DetailListViewItemTemplate +***********************************************************************/ + + void DetailListViewItemTemplate::OnInitialize() + { + DefaultListViewItemTemplate::OnInitialize(); + columnItemView = dynamic_cast(listControl->GetItemProvider()->RequestView(ListViewColumnItemArranger::IColumnItemView::Identifier)); + + { + textTable = new GuiTableComposition; + textTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); + textTable->SetRowsAndColumns(1, 1); + textTable->SetRowOption(0, GuiCellOption::MinSizeOption()); + textTable->SetColumnOption(0, GuiCellOption::AbsoluteOption(0)); + AddChild(textTable); + { + auto cell = new GuiCellComposition; + textTable->AddChild(cell); + cell->SetSite(0, 0, 1, 1); + + auto table = new GuiTableComposition; + cell->AddChild(table); + table->SetRowsAndColumns(3, 2); + table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); + table->SetRowOption(1, GuiCellOption::MinSizeOption()); + table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); + table->SetColumnOption(0, GuiCellOption::MinSizeOption()); + table->SetColumnOption(1, GuiCellOption::PercentageOption(1.0)); + table->SetAlignmentToParent(Margin(0, 0, 0, 0)); + table->SetCellPadding(2); + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(1, 0, 1, 1); + cell->SetPreferredMinSize(Size(16, 16)); + + image = GuiImageFrameElement::Create(); + image->SetStretch(true); + cell->SetOwnedElement(image); + } + { + auto cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 1, 3, 1); + cell->SetMargin(Margin(0, 0, 8, 0)); + + text = GuiSolidLabelElement::Create(); + text->SetAlignments(Alignment::Left, Alignment::Center); + text->SetEllipse(true); + cell->SetOwnedElement(text); + } + } + } + + if (auto listView = dynamic_cast(listControl)) + { + auto itemIndex = GetIndex(); + if (auto view = dynamic_cast(listView->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + auto imageData = view->GetSmallImage(itemIndex); + if (imageData) + { + image->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); + } + else + { + image->SetImage(0); + } + text->SetText(view->GetText(itemIndex)); + text->SetColor(listView->GetControlTemplateObject(true)->GetPrimaryTextColor()); + + vint columnCount = view->GetColumnCount() - 1; + subItems.Resize(columnCount); + textTable->SetRowsAndColumns(1, columnCount + 1); + for (vint i = 0; i < columnCount; i++) + { + auto cell = new GuiCellComposition; + textTable->AddChild(cell); + cell->SetSite(0, i + 1, 1, 1); + cell->SetMargin(Margin(8, 0, 8, 0)); + + subItems[i] = GuiSolidLabelElement::Create(); + subItems[i]->SetAlignments(Alignment::Left, Alignment::Center); + subItems[i]->SetFont(text->GetFont()); + subItems[i]->SetEllipse(true); + subItems[i]->SetText(view->GetSubItem(itemIndex, i)); + subItems[i]->SetColor(listView->GetControlTemplateObject(true)->GetSecondaryTextColor()); + cell->SetOwnedElement(subItems[i]); + } + OnColumnChanged(); + } + } + + FontChanged.AttachMethod(this, &DetailListViewItemTemplate::OnFontChanged); + + FontChanged.Execute(compositions::GuiEventArgs(this)); + } + + void DetailListViewItemTemplate::OnColumnChanged() + { + if (auto view = dynamic_cast(listControl->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + if (columnItemView) + { + vint columnCount = view->GetColumnCount(); + if (columnCount>textTable->GetColumns()) + { + columnCount = textTable->GetColumns(); + } + for (vint i = 0; iSetColumnOption(i, GuiCellOption::AbsoluteOption(columnItemView->GetColumnSize(i))); + } + textTable->UpdateCellBounds(); + } + } + } + + void DetailListViewItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + text->SetFont(GetFont()); + if (auto view = dynamic_cast(listControl->GetItemProvider()->RequestView(IListViewItemView::Identifier))) + { + vint columnCount = view->GetColumnCount() - 1; + for (vint i = 0; i < columnCount; i++) + { + subItems[i]->SetFont(GetFont()); + } + } + } + + DetailListViewItemTemplate::DetailListViewItemTemplate() + { + } + + DetailListViewItemTemplate::~DetailListViewItemTemplate() + { + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\LISTCONTROLPACKAGE\GUITEXTLISTCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace elements; + using namespace compositions; + using namespace reflection::description; + + namespace list + { + const wchar_t* const ITextItemView::Identifier = L"vl::presentation::controls::list::ITextItemView"; + +/*********************************************************************** +DefaultTextListItemTemplate +***********************************************************************/ + + TemplateProperty DefaultTextListItemTemplate::CreateBulletStyle() + { + return {}; + } + + void DefaultTextListItemTemplate::OnInitialize() + { + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + textElement = GuiSolidLabelElement::Create(); + textElement->SetAlignments(Alignment::Left, Alignment::Center); + + GuiBoundsComposition* textComposition = new GuiBoundsComposition; + textComposition->SetOwnedElement(textElement); + textComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); + + if (auto bulletStyleController = CreateBulletStyle()) + { + bulletButton = new GuiSelectableButton(theme::ThemeName::Unknown); + bulletButton->SetControlTemplate(bulletStyleController); + bulletButton->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + bulletButton->SelectedChanged.AttachMethod(this, &DefaultTextListItemTemplate::OnBulletSelectedChanged); + + GuiTableComposition* table = new GuiTableComposition; + AddChild(table); + table->SetAlignmentToParent(Margin(0, 0, 0, 0)); + table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + table->SetRowsAndColumns(1, 2); + table->SetRowOption(0, GuiCellOption::PercentageOption(1.0)); + table->SetColumnOption(0, GuiCellOption::MinSizeOption()); + table->SetColumnOption(1, GuiCellOption::PercentageOption(1.0)); + { + GuiCellComposition* cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 0, 1, 1); + cell->AddChild(bulletButton->GetBoundsComposition()); + } + { + GuiCellComposition* cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 1, 1, 1); + cell->AddChild(textComposition); + textComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + } + } + else + { + AddChild(textComposition); + textComposition->SetAlignmentToParent(Margin(5, 0, 0, 0)); + } + + FontChanged.AttachMethod(this, &DefaultTextListItemTemplate::OnFontChanged); + TextChanged.AttachMethod(this, &DefaultTextListItemTemplate::OnTextChanged); + TextColorChanged.AttachMethod(this, &DefaultTextListItemTemplate::OnTextColorChanged); + CheckedChanged.AttachMethod(this, &DefaultTextListItemTemplate::OnCheckedChanged); + + FontChanged.Execute(compositions::GuiEventArgs(this)); + TextChanged.Execute(compositions::GuiEventArgs(this)); + TextColorChanged.Execute(compositions::GuiEventArgs(this)); + CheckedChanged.Execute(compositions::GuiEventArgs(this)); + } + + void DefaultTextListItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetFont(GetFont()); + } + + void DefaultTextListItemTemplate::OnTextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetText(GetText()); + } + + void DefaultTextListItemTemplate::OnTextColorChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetColor(GetTextColor()); + } + + void DefaultTextListItemTemplate::OnCheckedChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (bulletButton) + { + supressEdit = true; + bulletButton->SetSelected(GetChecked()); + supressEdit = false; + } + } + + void DefaultTextListItemTemplate::OnBulletSelectedChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (!supressEdit) + { + if (auto textItemView = dynamic_cast(listControl->GetItemProvider()->RequestView(ITextItemView::Identifier))) + { + BeginEditListItem(); + textItemView->SetChecked(GetIndex(), bulletButton->GetSelected()); + EndEditListItem(); + } + } + } + + DefaultTextListItemTemplate::DefaultTextListItemTemplate() + { + } + + DefaultTextListItemTemplate::~DefaultTextListItemTemplate() + { + } + +/*********************************************************************** +DefaultCheckTextListItemTemplate +***********************************************************************/ + + TemplateProperty DefaultCheckTextListItemTemplate::CreateBulletStyle() + { + if (auto textList = dynamic_cast(listControl)) + { + auto style = textList->GetControlTemplateObject(true)->GetCheckBulletTemplate(); + if (style) return style; + } + return theme::GetCurrentTheme()->CreateStyle(theme::ThemeName::CheckTextListItem); + } + +/*********************************************************************** +DefaultRadioTextListItemTemplate +***********************************************************************/ + + TemplateProperty DefaultRadioTextListItemTemplate::CreateBulletStyle() + { + if (auto textList = dynamic_cast(listControl)) + { + auto style = textList->GetControlTemplateObject(true)->GetRadioBulletTemplate(); + if (style) return style; + } + return theme::GetCurrentTheme()->CreateStyle(theme::ThemeName::RadioTextListItem); + } + +/*********************************************************************** +TextItem +***********************************************************************/ + + TextItem::TextItem() + :owner(0) + , checked(false) + { + } + + TextItem::TextItem(const WString& _text, bool _checked) + :owner(0) + , text(_text) + , checked(_checked) + { + } + + TextItem::~TextItem() + { + } + + bool TextItem::operator==(const TextItem& value)const + { + return text==value.text; + } + + bool TextItem::operator!=(const TextItem& value)const + { + return text!=value.text; + } + + const WString& TextItem::GetText() + { + return text; + } + + void TextItem::SetText(const WString& value) + { + if (text != value) + { + text = value; + if (owner) + { + vint index = owner->IndexOf(this); + owner->InvokeOnItemModified(index, 1, 1); + } + } + } + + bool TextItem::GetChecked() + { + return checked; + } + + void TextItem::SetChecked(bool value) + { + if (checked != value) + { + checked = value; + if (owner) + { + vint index = owner->IndexOf(this); + owner->InvokeOnItemModified(index, 1, 1); + + GuiItemEventArgs arguments; + arguments.itemIndex = index; + owner->listControl->ItemChecked.Execute(arguments); + } + } + } + +/*********************************************************************** +TextItemProvider +***********************************************************************/ + + void TextItemProvider::AfterInsert(vint item, const Ptr& value) + { + ListProvider>::AfterInsert(item, value); + value->owner = this; + } + + void TextItemProvider::BeforeRemove(vint item, const Ptr& value) + { + value->owner = 0; + ListProvider>::BeforeRemove(item, value); + } + + WString TextItemProvider::GetTextValue(vint itemIndex) + { + return Get(itemIndex)->GetText(); + } + + description::Value TextItemProvider::GetBindingValue(vint itemIndex) + { + return Value::From(Get(itemIndex)); + } + + bool TextItemProvider::GetChecked(vint itemIndex) + { + return Get(itemIndex)->GetChecked(); + } + + void TextItemProvider::SetChecked(vint itemIndex, bool value) + { + return Get(itemIndex)->SetChecked(value); + } + + TextItemProvider::TextItemProvider() + :listControl(0) + { + } + + TextItemProvider::~TextItemProvider() + { + } + + IDescriptable* TextItemProvider::RequestView(const WString& identifier) + { + if (identifier == ITextItemView::Identifier) + { + return (ITextItemView*)this; + } + else + { + return nullptr; + } + } + } + +/*********************************************************************** +GuiTextList +***********************************************************************/ + + void GuiVirtualTextList::BeforeControlTemplateUninstalled_() + { + } + + void GuiVirtualTextList::AfterControlTemplateInstalled_(bool initialize) + { + } + + void GuiVirtualTextList::OnStyleInstalled(vint itemIndex, ItemStyle* style) + { + GuiSelectableListControl::OnStyleInstalled(itemIndex, style); + if (auto textItemStyle = dynamic_cast(style)) + { + textItemStyle->SetTextColor(GetControlTemplateObject(true)->GetTextColor()); + if (auto textItemView = dynamic_cast(itemProvider->RequestView(list::ITextItemView::Identifier))) + { + textItemStyle->SetChecked(textItemView->GetChecked(itemIndex)); + } + } + } + + void GuiVirtualTextList::OnItemTemplateChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + view = TextListView::Unknown; + } + + GuiVirtualTextList::GuiVirtualTextList(theme::ThemeName themeName, GuiListControl::IItemProvider* _itemProvider) + :GuiSelectableListControl(themeName, _itemProvider) + { + ItemTemplateChanged.AttachMethod(this, &GuiVirtualTextList::OnItemTemplateChanged); + ItemChecked.SetAssociatedComposition(boundsComposition); + + SetView(TextListView::Text); + } + + GuiVirtualTextList::~GuiVirtualTextList() + { + } + + TextListView GuiVirtualTextList::GetView() + { + return view; + } + + void GuiVirtualTextList::SetView(TextListView _view) + { + switch (_view) + { + case TextListView::Text: + SetStyleAndArranger( + [](const Value&) { return new list::DefaultTextListItemTemplate; }, + new list::FixedHeightItemArranger + ); + break; + case TextListView::Check: + SetStyleAndArranger( + [](const Value&) { return new list::DefaultCheckTextListItemTemplate; }, + new list::FixedHeightItemArranger + ); + break; + case TextListView::Radio: + SetStyleAndArranger( + [](const Value&) { return new list::DefaultRadioTextListItemTemplate; }, + new list::FixedHeightItemArranger + ); + break; + default:; + } + view = _view; + } + +/*********************************************************************** +GuiTextList +***********************************************************************/ + + GuiTextList::GuiTextList(theme::ThemeName themeName) + :GuiVirtualTextList(themeName, new list::TextItemProvider) + { + items=dynamic_cast(itemProvider.Obj()); + items->listControl=this; + } + + GuiTextList::~GuiTextList() + { + } + + list::TextItemProvider& GuiTextList::GetItems() + { + return *items; + } + + Ptr GuiTextList::GetSelectedItem() + { + vint index = GetSelectedItemIndex(); + if (index == -1) return 0; + return items->Get(index); + } + } + } +} + +/*********************************************************************** +.\CONTROLS\LISTCONTROLPACKAGE\GUITREEVIEWCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace compositions; + using namespace reflection::description; + + namespace tree + { + const wchar_t* const INodeItemView::Identifier = L"vl::presentation::controls::tree::INodeItemView"; + +/*********************************************************************** +NodeItemProvider +***********************************************************************/ + + Ptr NodeItemProvider::GetNodeByOffset(Ptr provider, vint offset) + { + if (offset == 0) return provider; + Ptr result; + if (provider->GetExpanding() && offset > 0) + { + offset -= 1; + vint count = provider->GetChildCount(); + for (vint i = 0; (!result && i < count); i++) + { + auto child = provider->GetChild(i); + vint visibleCount = child->CalculateTotalVisibleNodes(); + if (offset < visibleCount) + { + result = GetNodeByOffset(child, offset); + } + else + { + offset -= visibleCount; + } + } + } + return result; + } + + void NodeItemProvider::OnAttached(INodeRootProvider* provider) + { + } + + void NodeItemProvider::OnBeforeItemModified(INodeProvider* parentNode, vint start, vint count, vint newCount) + { + vint offset = 0; + vint base = CalculateNodeVisibilityIndexInternal(parentNode); + if (base != -2 && parentNode->GetExpanding()) + { + for (vint i = 0; i < count; i++) + { + auto child = parentNode->GetChild(start + i); + offset += child->CalculateTotalVisibleNodes(); + } + } + offsetBeforeChildModifieds.Set(parentNode, offset); + } + + void NodeItemProvider::OnAfterItemModified(INodeProvider* parentNode, vint start, vint count, vint newCount) + { + vint offsetBeforeChildModified = 0; + { + vint index = offsetBeforeChildModifieds.Keys().IndexOf(parentNode); + if (index != -1) + { + offsetBeforeChildModified = offsetBeforeChildModifieds.Values().Get(index); + offsetBeforeChildModifieds.Remove(parentNode); + } + } + + vint base = CalculateNodeVisibilityIndexInternal(parentNode); + if (base != -2 && parentNode->GetExpanding()) + { + vint offset = 0; + vint firstChildStart = -1; + for (vint i = 0; i < newCount; i++) + { + auto child = parentNode->GetChild(start + i); + if (i == 0) + { + firstChildStart = CalculateNodeVisibilityIndexInternal(child.Obj()); + } + offset += child->CalculateTotalVisibleNodes(); + } + + if (firstChildStart == -1) + { + vint childCount = parentNode->GetChildCount(); + if (childCount == 0) + { + firstChildStart = base + 1; + } + else if (start < childCount) + { + auto child = parentNode->GetChild(start); + firstChildStart = CalculateNodeVisibilityIndexInternal(child.Obj()); + } + else + { + auto child = parentNode->GetChild(start - 1); + firstChildStart = CalculateNodeVisibilityIndexInternal(child.Obj()); + firstChildStart += child->CalculateTotalVisibleNodes(); + } + } + InvokeOnItemModified(firstChildStart, offsetBeforeChildModified, offset); + } + } + + void NodeItemProvider::OnItemExpanded(INodeProvider* node) + { + vint base = CalculateNodeVisibilityIndexInternal(node); + if (base != -2) + { + vint visibility = node->CalculateTotalVisibleNodes(); + InvokeOnItemModified(base + 1, 0, visibility - 1); + } + } + + void NodeItemProvider::OnItemCollapsed(INodeProvider* node) + { + vint base = CalculateNodeVisibilityIndexInternal(node); + if (base != -2) + { + vint visibility = 0; + vint count = node->GetChildCount(); + for (vint i = 0; i < count; i++) + { + auto child = node->GetChild(i); + visibility += child->CalculateTotalVisibleNodes(); + } + InvokeOnItemModified(base + 1, visibility, 0); + } + } + + vint NodeItemProvider::CalculateNodeVisibilityIndexInternal(INodeProvider* node) + { + auto parent = node->GetParent(); + if (!parent) + { + return -1; + } + if (!parent->GetExpanding()) + { + return -2; + } + + vint index = CalculateNodeVisibilityIndexInternal(parent.Obj()); + if (index == -2) + { + return -2; + } + + vint count = parent->GetChildCount(); + for (vint i = 0; i < count; i++) + { + auto child = parent->GetChild(i); + bool findResult = child == node; + if (findResult) + { + index++; + } + else + { + index += child->CalculateTotalVisibleNodes(); + } + if (findResult) + { + return index; + } + } + return -1; + } + + vint NodeItemProvider::CalculateNodeVisibilityIndex(INodeProvider* node) + { + vint result = CalculateNodeVisibilityIndexInternal(node); + return result < 0 ? -1 : result; + } + + Ptr NodeItemProvider::RequestNode(vint index) + { + if(root->CanGetNodeByVisibleIndex()) + { + return root->GetNodeByVisibleIndex(index+1); + } + else + { + return GetNodeByOffset(root->GetRootNode(), index+1); + } + } + + NodeItemProvider::NodeItemProvider(Ptr _root) + :root(_root) + { + root->AttachCallback(this); + } + + NodeItemProvider::~NodeItemProvider() + { + root->DetachCallback(this); + } + + Ptr NodeItemProvider::GetRoot() + { + return root; + } + + vint NodeItemProvider::Count() + { + return root->GetRootNode()->CalculateTotalVisibleNodes()-1; + } + + WString NodeItemProvider::GetTextValue(vint itemIndex) + { + if (auto node = RequestNode(itemIndex)) + { + return root->GetTextValue(node.Obj()); + } + return L""; + } + + description::Value NodeItemProvider::GetBindingValue(vint itemIndex) + { + if (auto node = RequestNode(itemIndex)) + { + return root->GetBindingValue(node.Obj()); + } + return Value(); + } + + IDescriptable* NodeItemProvider::RequestView(const WString& identifier) + { + if(identifier==INodeItemView::Identifier) + { + return (INodeItemView*)this; + } + else + { + return root->RequestView(identifier); + } + } + +/*********************************************************************** +MemoryNodeProvider::NodeCollection +***********************************************************************/ + + void MemoryNodeProvider::NodeCollection::OnBeforeChildModified(vint start, vint count, vint newCount) + { + if (ownerProvider->expanding) + { + for (vint i = 0; i < count; i++) + { + offsetBeforeChildModified += items[start + i]->totalVisibleNodeCount; + } + } + INodeProviderCallback* proxy = ownerProvider->GetCallbackProxyInternal(); + if (proxy) + { + proxy->OnBeforeItemModified(ownerProvider, start, count, newCount); + } + } + + void MemoryNodeProvider::NodeCollection::OnAfterChildModified(vint start, vint count, vint newCount) + { + ownerProvider->childCount += (newCount - count); + if (ownerProvider->expanding) + { + vint offset = 0; + for (vint i = 0; i < newCount; i++) + { + offset += items[start + i]->totalVisibleNodeCount; + } + ownerProvider->OnChildTotalVisibleNodesChanged(offset - offsetBeforeChildModified); + } + offsetBeforeChildModified = 0; + INodeProviderCallback* proxy = ownerProvider->GetCallbackProxyInternal(); + if (proxy) + { + proxy->OnAfterItemModified(ownerProvider, start, count, newCount); + } + } + + bool MemoryNodeProvider::NodeCollection::QueryInsert(vint index, Ptr const& child) + { + return child->parent == 0; + } + + bool MemoryNodeProvider::NodeCollection::QueryRemove(vint index, Ptr const& child) + { + return child->parent == ownerProvider; + } + + void MemoryNodeProvider::NodeCollection::BeforeInsert(vint index, Ptr const& child) + { + OnBeforeChildModified(index, 0, 1); + child->parent = ownerProvider; + } + + void MemoryNodeProvider::NodeCollection::BeforeRemove(vint index, Ptr const& child) + { + OnBeforeChildModified(index, 1, 0); + child->parent = 0; + } + + void MemoryNodeProvider::NodeCollection::AfterInsert(vint index, Ptr const& child) + { + OnAfterChildModified(index, 0, 1); + } + + void MemoryNodeProvider::NodeCollection::AfterRemove(vint index, vint count) + { + OnAfterChildModified(index, count, 0); + } + + MemoryNodeProvider::NodeCollection::NodeCollection() + :ownerProvider(0) + { + } + +/*********************************************************************** +MemoryNodeProvider +***********************************************************************/ + + INodeProviderCallback* MemoryNodeProvider::GetCallbackProxyInternal() + { + if(parent) + { + return parent->GetCallbackProxyInternal(); + } + else + { + return 0; + } + } + + void MemoryNodeProvider::OnChildTotalVisibleNodesChanged(vint offset) + { + totalVisibleNodeCount+=offset; + if(parent) + { + parent->OnChildTotalVisibleNodesChanged(offset); + } + } + + MemoryNodeProvider::MemoryNodeProvider(Ptr _data) + :data(_data) + { + children.ownerProvider=this; + } + + MemoryNodeProvider::~MemoryNodeProvider() + { + } + + Ptr MemoryNodeProvider::GetData() + { + return data; + } + + void MemoryNodeProvider::SetData(const Ptr& value) + { + data=value; + NotifyDataModified(); + } + + void MemoryNodeProvider::NotifyDataModified() + { + if(parent) + { + vint index=parent->children.IndexOf(this); + INodeProviderCallback* proxy=GetCallbackProxyInternal(); + if(proxy) + { + proxy->OnBeforeItemModified(parent, index, 1, 1); + proxy->OnAfterItemModified(parent, index, 1, 1); + } + } + } + + MemoryNodeProvider::NodeCollection& MemoryNodeProvider::Children() + { + return children; + } + + bool MemoryNodeProvider::GetExpanding() + { + return expanding; + } + + void MemoryNodeProvider::SetExpanding(bool value) + { + if(expanding!=value) + { + expanding=value; + vint offset=0; + for(vint i=0;itotalVisibleNodeCount; + } + + OnChildTotalVisibleNodesChanged(expanding?offset:-offset); + INodeProviderCallback* proxy=GetCallbackProxyInternal(); + if(proxy) + { + if(expanding) + { + proxy->OnItemExpanded(this); + } + else + { + proxy->OnItemCollapsed(this); + } + } + } + } + + vint MemoryNodeProvider::CalculateTotalVisibleNodes() + { + return totalVisibleNodeCount; + } + + vint MemoryNodeProvider::GetChildCount() + { + return childCount; + } + + Ptr MemoryNodeProvider::GetParent() + { + return parent; + } + + Ptr MemoryNodeProvider::GetChild(vint index) + { + if(0<=index && indexOnBeforeItemModified(parentNode, start, count, newCount); + } + } + + void NodeRootProviderBase::OnAfterItemModified(INodeProvider* parentNode, vint start, vint count, vint newCount) + { + for(vint i=0;iOnAfterItemModified(parentNode, start, count, newCount); + } + } + + void NodeRootProviderBase::OnItemExpanded(INodeProvider* node) + { + for(vint i=0;iOnItemExpanded(node); + } + } + + void NodeRootProviderBase::OnItemCollapsed(INodeProvider* node) + { + for(vint i=0;iOnItemCollapsed(node); + } + } + + NodeRootProviderBase::NodeRootProviderBase() + { + } + + NodeRootProviderBase::~NodeRootProviderBase() + { + } + + bool NodeRootProviderBase::CanGetNodeByVisibleIndex() + { + return false; + } + + Ptr NodeRootProviderBase::GetNodeByVisibleIndex(vint index) + { + return nullptr; + } + + bool NodeRootProviderBase::AttachCallback(INodeProviderCallback* value) + { + if(callbacks.Contains(value)) + { + return false; + } + else + { + callbacks.Add(value); + value->OnAttached(this); + return true; + } + } + + bool NodeRootProviderBase::DetachCallback(INodeProviderCallback* value) + { + vint index=callbacks.IndexOf(value); + if(index==-1) + { + return false; + } + else + { + value->OnAttached(0); + callbacks.Remove(value); + return true; + } + } + + IDescriptable* NodeRootProviderBase::RequestView(const WString& identifier) + { + return 0; + } + +/*********************************************************************** +MemoryNodeRootProvider +***********************************************************************/ + + INodeProviderCallback* MemoryNodeRootProvider::GetCallbackProxyInternal() + { + return this; + } + + MemoryNodeRootProvider::MemoryNodeRootProvider() + { + SetExpanding(true); + } + + MemoryNodeRootProvider::~MemoryNodeRootProvider() + { + } + + Ptr MemoryNodeRootProvider::GetRootNode() + { + return this; + } + + MemoryNodeProvider* MemoryNodeRootProvider::GetMemoryNode(INodeProvider* node) + { + return dynamic_cast(node); + } + } + +/*********************************************************************** +GuiVirtualTreeListControl +***********************************************************************/ + + void GuiVirtualTreeListControl::BeforeControlTemplateUninstalled_() + { + } + + void GuiVirtualTreeListControl::AfterControlTemplateInstalled_(bool initialize) + { + } + + void GuiVirtualTreeListControl::OnAttached(tree::INodeRootProvider* provider) + { + } + + void GuiVirtualTreeListControl::OnBeforeItemModified(tree::INodeProvider* parentNode, vint start, vint count, vint newCount) + { + } + + void GuiVirtualTreeListControl::OnAfterItemModified(tree::INodeProvider* parentNode, vint start, vint count, vint newCount) + { + } + + void GuiVirtualTreeListControl::OnItemExpanded(tree::INodeProvider* node) + { + GuiNodeEventArgs arguments; + (GuiEventArgs&)arguments=GetNotifyEventArguments(); + arguments.node=node; + NodeExpanded.Execute(arguments); + } + + void GuiVirtualTreeListControl::OnItemCollapsed(tree::INodeProvider* node) + { + GuiNodeEventArgs arguments; + (GuiEventArgs&)arguments=GetNotifyEventArguments(); + arguments.node=node; + NodeCollapsed.Execute(arguments); + } + + void GuiVirtualTreeListControl::OnItemMouseEvent(compositions::GuiNodeMouseEvent& nodeEvent, compositions::GuiGraphicsComposition* sender, compositions::GuiItemMouseEventArgs& arguments) + { + auto node = GetNodeItemView()->RequestNode(arguments.itemIndex); + if (node) + { + GuiNodeMouseEventArgs redirectArguments; + (GuiMouseEventArgs&)redirectArguments = arguments; + redirectArguments.node = node.Obj(); + nodeEvent.Execute(redirectArguments); + (GuiMouseEventArgs&)arguments = redirectArguments; + } + } + + void GuiVirtualTreeListControl::OnItemNotifyEvent(compositions::GuiNodeNotifyEvent& nodeEvent, compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) + { + if (auto node = GetNodeItemView()->RequestNode(arguments.itemIndex)) + { + GuiNodeEventArgs redirectArguments; + (GuiEventArgs&)redirectArguments = arguments; + redirectArguments.node = node.Obj(); + nodeEvent.Execute(redirectArguments); + (GuiEventArgs&)arguments = redirectArguments; + } + } + +#define ATTACH_ITEM_MOUSE_EVENT(NODEEVENTNAME, ITEMEVENTNAME)\ + {\ + Func func(this, &GuiVirtualTreeListControl::OnItemMouseEvent);\ + ITEMEVENTNAME.AttachFunction(Curry(func)(NODEEVENTNAME));\ + }\ + +#define ATTACH_ITEM_NOTIFY_EVENT(NODEEVENTNAME, ITEMEVENTNAME)\ + {\ + Func func(this, &GuiVirtualTreeListControl::OnItemNotifyEvent);\ + ITEMEVENTNAME.AttachFunction(Curry(func)(NODEEVENTNAME));\ + }\ + + void GuiVirtualTreeListControl::OnNodeLeftButtonDoubleClick(compositions::GuiGraphicsComposition* sender, compositions::GuiNodeMouseEventArgs& arguments) + { + if (arguments.node->GetChildCount() > 0) + { + arguments.node->SetExpanding(!arguments.node->GetExpanding()); + } + } + + GuiVirtualTreeListControl::GuiVirtualTreeListControl(theme::ThemeName themeName, Ptr _nodeRootProvider) + :GuiSelectableListControl(themeName, new tree::NodeItemProvider(_nodeRootProvider)) + { + nodeItemProvider = dynamic_cast(GetItemProvider()); + nodeItemView = dynamic_cast(GetItemProvider()->RequestView(tree::INodeItemView::Identifier)); + + NodeLeftButtonDown.SetAssociatedComposition(boundsComposition); + NodeLeftButtonUp.SetAssociatedComposition(boundsComposition); + NodeLeftButtonDoubleClick.SetAssociatedComposition(boundsComposition); + NodeMiddleButtonDown.SetAssociatedComposition(boundsComposition); + NodeMiddleButtonUp.SetAssociatedComposition(boundsComposition); + NodeMiddleButtonDoubleClick.SetAssociatedComposition(boundsComposition); + NodeRightButtonDown.SetAssociatedComposition(boundsComposition); + NodeRightButtonUp.SetAssociatedComposition(boundsComposition); + NodeRightButtonDoubleClick.SetAssociatedComposition(boundsComposition); + NodeMouseMove.SetAssociatedComposition(boundsComposition); + NodeMouseEnter.SetAssociatedComposition(boundsComposition); + NodeMouseLeave.SetAssociatedComposition(boundsComposition); + NodeExpanded.SetAssociatedComposition(boundsComposition); + NodeCollapsed.SetAssociatedComposition(boundsComposition); + + ATTACH_ITEM_MOUSE_EVENT(NodeLeftButtonDown, ItemLeftButtonDown); + ATTACH_ITEM_MOUSE_EVENT(NodeLeftButtonUp, ItemLeftButtonUp); + ATTACH_ITEM_MOUSE_EVENT(NodeLeftButtonDoubleClick, ItemLeftButtonDoubleClick); + ATTACH_ITEM_MOUSE_EVENT(NodeMiddleButtonDown, ItemMiddleButtonDown); + ATTACH_ITEM_MOUSE_EVENT(NodeMiddleButtonUp, ItemMiddleButtonUp); + ATTACH_ITEM_MOUSE_EVENT(NodeMiddleButtonDoubleClick, ItemMiddleButtonDoubleClick); + ATTACH_ITEM_MOUSE_EVENT(NodeRightButtonDown, ItemRightButtonDown); + ATTACH_ITEM_MOUSE_EVENT(NodeRightButtonUp, ItemRightButtonUp); + ATTACH_ITEM_MOUSE_EVENT(NodeRightButtonDoubleClick, ItemRightButtonDoubleClick); + ATTACH_ITEM_MOUSE_EVENT(NodeMouseMove, ItemMouseMove); + ATTACH_ITEM_NOTIFY_EVENT(NodeMouseEnter, ItemMouseEnter); + ATTACH_ITEM_NOTIFY_EVENT(NodeMouseLeave, ItemMouseLeave); + + nodeItemProvider->GetRoot()->AttachCallback(this); + NodeLeftButtonDoubleClick.AttachMethod(this, &GuiVirtualTreeListControl::OnNodeLeftButtonDoubleClick); + } + +#undef ATTACH_ITEM_MOUSE_EVENT +#undef ATTACH_ITEM_NOTIFY_EVENT + + GuiVirtualTreeListControl::~GuiVirtualTreeListControl() + { + } + + tree::INodeItemView* GuiVirtualTreeListControl::GetNodeItemView() + { + return nodeItemView; + } + + tree::INodeRootProvider* GuiVirtualTreeListControl::GetNodeRootProvider() + { + return nodeItemProvider->GetRoot().Obj(); + } + + namespace tree + { + +/*********************************************************************** +TreeViewItem +***********************************************************************/ + + const wchar_t* const ITreeViewItemView::Identifier = L"vl::presentation::controls::tree::ITreeViewItemView"; + + TreeViewItem::TreeViewItem() + { + } + + TreeViewItem::TreeViewItem(const Ptr& _image, const WString& _text) + :image(_image) + ,text(_text) + { + } + +/*********************************************************************** +TreeViewItemRootProvider +***********************************************************************/ + + Ptr TreeViewItemRootProvider::GetNodeImage(INodeProvider* node) + { + MemoryNodeProvider* memoryNode=dynamic_cast(node); + if(memoryNode) + { + Ptr data=memoryNode->GetData().Cast(); + if(data) + { + return data->image; + } + } + return 0; + } + + WString TreeViewItemRootProvider::GetTextValue(INodeProvider* node) + { + MemoryNodeProvider* memoryNode = dynamic_cast(node); + if (memoryNode) + { + Ptr data = memoryNode->GetData().Cast(); + if (data) + { + return data->text; + } + } + return L""; + } + + description::Value TreeViewItemRootProvider::GetBindingValue(INodeProvider* node) + { + return Value::From(GetTreeViewData(node)); + } + + TreeViewItemRootProvider::TreeViewItemRootProvider() + { + } + + TreeViewItemRootProvider::~TreeViewItemRootProvider() + { + } + + IDescriptable* TreeViewItemRootProvider::RequestView(const WString& identifier) + { + if(identifier==ITreeViewItemView::Identifier) + { + return (ITreeViewItemView*)this; + } + else + { + return MemoryNodeRootProvider::RequestView(identifier); + } + } + + Ptr TreeViewItemRootProvider::GetTreeViewData(INodeProvider* node) + { + MemoryNodeProvider* memoryNode=GetMemoryNode(node); + if(memoryNode) + { + return memoryNode->GetData().Cast(); + } + else + { + return 0; + } + } + + void TreeViewItemRootProvider::SetTreeViewData(INodeProvider* node, Ptr value) + { + MemoryNodeProvider* memoryNode=GetMemoryNode(node); + if(memoryNode) + { + memoryNode->SetData(value); + } + } + + void TreeViewItemRootProvider::UpdateTreeViewData(INodeProvider* node) + { + MemoryNodeProvider* memoryNode=GetMemoryNode(node); + if(memoryNode) + { + memoryNode->NotifyDataModified(); + } + } + } + +/*********************************************************************** +GuiVirtualTreeView +***********************************************************************/ + + templates::GuiTreeItemTemplate* GuiVirtualTreeView::GetStyleFromNode(tree::INodeProvider* node) + { + if (itemArranger) + { + vint index = nodeItemView->CalculateNodeVisibilityIndex(node); + if (index != -1) + { + auto style = itemArranger->GetVisibleStyle(index); + return dynamic_cast(style); + } + } + return nullptr; + } + + void GuiVirtualTreeView::SetStyleExpanding(tree::INodeProvider* node, bool expanding) + { + if (auto treeItemStyle = GetStyleFromNode(node)) + { + treeItemStyle->SetExpanding(expanding); + } + } + + void GuiVirtualTreeView::SetStyleExpandable(tree::INodeProvider* node, bool expandable) + { + if (auto treeItemStyle = GetStyleFromNode(node)) + { + treeItemStyle->SetExpandable(expandable); + } + } + + void GuiVirtualTreeView::OnAfterItemModified(tree::INodeProvider* parentNode, vint start, vint count, vint newCount) + { + GuiVirtualTreeListControl::OnAfterItemModified(parentNode, start, count, newCount); + SetStyleExpandable(parentNode, parentNode->GetChildCount() > 0); + } + + void GuiVirtualTreeView::OnItemExpanded(tree::INodeProvider* node) + { + GuiVirtualTreeListControl::OnItemExpanded(node); + SetStyleExpanding(node, true); + } + + void GuiVirtualTreeView::OnItemCollapsed(tree::INodeProvider* node) + { + GuiVirtualTreeListControl::OnItemCollapsed(node); + SetStyleExpanding(node, false); + } + + void GuiVirtualTreeView::OnStyleInstalled(vint itemIndex, ItemStyle* style) + { + GuiVirtualTreeListControl::OnStyleInstalled(itemIndex, style); + if (auto treeItemStyle = dynamic_cast(style)) + { + treeItemStyle->SetTextColor(GetControlTemplateObject(true)->GetTextColor()); + + if (treeViewItemView) + { + if (auto node = nodeItemView->RequestNode(itemIndex)) + { + treeItemStyle->SetImage(treeViewItemView->GetNodeImage(node.Obj())); + treeItemStyle->SetExpanding(node->GetExpanding()); + treeItemStyle->SetExpandable(node->GetChildCount() > 0); + { + vint level = -1; + auto current = node; + while (current->GetParent()) + { + level++; + current = current->GetParent(); + } + treeItemStyle->SetLevel(level); + } + } + } + } + } + + GuiVirtualTreeView::GuiVirtualTreeView(theme::ThemeName themeName, Ptr _nodeRootProvider) + :GuiVirtualTreeListControl(themeName, _nodeRootProvider) + { + treeViewItemView = dynamic_cast(GetNodeRootProvider()->RequestView(tree::ITreeViewItemView::Identifier)); + SetStyleAndArranger( + [](const Value&) { return new tree::DefaultTreeItemTemplate; }, + new list::FixedHeightItemArranger + ); + } + + GuiVirtualTreeView::~GuiVirtualTreeView() + { + } + +/*********************************************************************** +GuiTreeView +***********************************************************************/ + + GuiTreeView::GuiTreeView(theme::ThemeName themeName) + :GuiVirtualTreeView(themeName, new tree::TreeViewItemRootProvider) + { + nodes = nodeItemProvider->GetRoot().Cast(); + } + + GuiTreeView::~GuiTreeView() + { + } + + Ptr GuiTreeView::Nodes() + { + return nodes; + } + + Ptr GuiTreeView::GetSelectedItem() + { + Ptr result; + vint index = GetSelectedItemIndex(); + if (index != -1) + { + if (auto node = nodeItemView->RequestNode(index)) + { + if (auto memoryNode = node.Cast()) + { + result = memoryNode->GetData().Cast(); + } + } + } + return result; + } + + namespace tree + { + +/*********************************************************************** +DefaultTreeItemTemplate +***********************************************************************/ + + void DefaultTreeItemTemplate::OnInitialize() + { + templates::GuiTreeItemTemplate::OnInitialize(); + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + table = new GuiTableComposition; + AddChild(table); + table->SetRowsAndColumns(3, 4); + table->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); + table->SetRowOption(1, GuiCellOption::MinSizeOption()); + table->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); + table->SetColumnOption(0, GuiCellOption::AbsoluteOption(0)); + table->SetColumnOption(1, GuiCellOption::MinSizeOption()); + table->SetColumnOption(2, GuiCellOption::MinSizeOption()); + table->SetColumnOption(3, GuiCellOption::MinSizeOption()); + table->SetAlignmentToParent(Margin(0, 0, 0, 0)); + table->SetCellPadding(2); + { + GuiCellComposition* cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 1, 3, 1); + cell->SetPreferredMinSize(Size(16, 16)); + + expandingButton = new GuiSelectableButton(theme::ThemeName::TreeItemExpander); + if (auto treeView = dynamic_cast(listControl)) + { + if (auto expanderStyle = treeView->GetControlTemplateObject(true)->GetExpandingDecoratorTemplate()) + { + expandingButton->SetControlTemplate(expanderStyle); + } + } + expandingButton->SetAutoSelection(false); + expandingButton->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + expandingButton->GetBoundsComposition()->GetEventReceiver()->leftButtonDoubleClick.AttachMethod(this, &DefaultTreeItemTemplate::OnExpandingButtonDoubleClick); + expandingButton->Clicked.AttachMethod(this, &DefaultTreeItemTemplate::OnExpandingButtonClicked); + cell->AddChild(expandingButton->GetBoundsComposition()); + } + { + GuiCellComposition* cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(1, 2, 1, 1); + cell->SetPreferredMinSize(Size(16, 16)); + + imageElement = GuiImageFrameElement::Create(); + imageElement->SetStretch(true); + cell->SetOwnedElement(imageElement); + } + { + GuiCellComposition* cell = new GuiCellComposition; + table->AddChild(cell); + cell->SetSite(0, 3, 3, 1); + cell->SetPreferredMinSize(Size(192, 0)); + + textElement = GuiSolidLabelElement::Create(); + textElement->SetAlignments(Alignment::Left, Alignment::Center); + textElement->SetEllipse(true); + cell->SetOwnedElement(textElement); + } + + FontChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnFontChanged); + TextChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnTextChanged); + TextColorChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnTextColorChanged); + ExpandingChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnExpandingChanged); + ExpandableChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnExpandableChanged); + LevelChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnLevelChanged); + ImageChanged.AttachMethod(this, &DefaultTreeItemTemplate::OnImageChanged); + + FontChanged.Execute(compositions::GuiEventArgs(this)); + TextChanged.Execute(compositions::GuiEventArgs(this)); + TextColorChanged.Execute(compositions::GuiEventArgs(this)); + ExpandingChanged.Execute(compositions::GuiEventArgs(this)); + ExpandableChanged.Execute(compositions::GuiEventArgs(this)); + LevelChanged.Execute(compositions::GuiEventArgs(this)); + ImageChanged.Execute(compositions::GuiEventArgs(this)); + } + + void DefaultTreeItemTemplate::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetFont(GetFont()); + } + + void DefaultTreeItemTemplate::OnTextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetText(GetText()); + } + + void DefaultTreeItemTemplate::OnTextColorChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetColor(GetTextColor()); + } + + void DefaultTreeItemTemplate::OnExpandingChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + expandingButton->SetSelected(GetExpanding()); + } + + void DefaultTreeItemTemplate::OnExpandableChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + expandingButton->SetVisible(GetExpandable()); + } + + void DefaultTreeItemTemplate::OnLevelChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + table->SetColumnOption(0, GuiCellOption::AbsoluteOption(GetLevel() * 12)); + } + + void DefaultTreeItemTemplate::OnImageChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (auto imageData = GetImage()) + { + imageElement->SetImage(imageData->GetImage(), imageData->GetFrameIndex()); + } + else + { + imageElement->SetImage(nullptr); + } + } + + void DefaultTreeItemTemplate::OnExpandingButtonDoubleClick(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + arguments.handled = true; + } + + void DefaultTreeItemTemplate::OnExpandingButtonClicked(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (expandingButton->GetVisuallyEnabled()) + { + if (auto treeControl = dynamic_cast(listControl)) + { + if (auto view = treeControl->GetNodeItemView()) + { + vint index = treeControl->GetArranger()->GetVisibleIndex(this); + if (index != -1) + { + if (auto node = view->RequestNode(index)) + { + bool expanding = node->GetExpanding(); + node->SetExpanding(!expanding); + } + } + } + } + } + } + + DefaultTreeItemTemplate::DefaultTreeItemTemplate() + { + } + + DefaultTreeItemTemplate::~DefaultTreeItemTemplate() + { + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEMPLATES\GUIANIMATION.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace reflection::description; + +/*********************************************************************** +GuiTimedAnimation +***********************************************************************/ + + class GuiTimedAnimation : public Object, public virtual IGuiAnimation + { + protected: + DateTime startTime; + vuint64_t time; + bool running = false; + + public: + GuiTimedAnimation() + { + } + + ~GuiTimedAnimation() + { + } + + void Start()override + { + startTime = DateTime::LocalTime(); + time = 0; + running = true; + } + + void Pause()override + { + time = GetTime(); + running = false; + } + + void Resume()override + { + startTime = DateTime::LocalTime(); + running = true; + } + + vuint64_t GetTime() + { + if (running) + { + return time + (DateTime::LocalTime().totalMilliseconds - startTime.totalMilliseconds); + } + else + { + return time; + } + } + }; + +/*********************************************************************** +GuiFiniteAnimation +***********************************************************************/ + + class GuiFiniteAnimation : public GuiTimedAnimation + { + protected: + vuint64_t length = 0; + Func run; + + public: + GuiFiniteAnimation(const Func& _run, vuint64_t _length) + :run(_run) + , length(_length) + { + } + + ~GuiFiniteAnimation() + { + } + + void Run()override + { + auto currentTime = GetTime(); + if (currentTime < length && run) + { + run(currentTime); + } + } + + bool GetStopped()override + { + return GetTime() >= length; + } + }; + +/*********************************************************************** +GuiInfiniteAnimation +***********************************************************************/ + + class GuiInfiniteAnimation : public GuiTimedAnimation + { + protected: + Func run; + + public: + GuiInfiniteAnimation(const Func& _run) + :run(_run) + { + } + + ~GuiInfiniteAnimation() + { + } + + void Run()override + { + if (run) + { + run(GetTime()); + } + } + + bool GetStopped()override + { + return false; + } + }; + +/*********************************************************************** +IGuiAnimation +***********************************************************************/ + + Ptr IGuiAnimation::CreateAnimation(const Func& run, vuint64_t milliseconds) + { + return new GuiFiniteAnimation(run, milliseconds); + } + + Ptr IGuiAnimation::CreateAnimation(const Func& run) + { + return new GuiInfiniteAnimation(run); + } + +/*********************************************************************** +IGuiAnimationCoroutine +***********************************************************************/ + + class GuiCoroutineAnimation : public Object, public virtual IGuiAnimationCoroutine::IImpl + { + protected: + IGuiAnimationCoroutine::Creator creator; + Ptr coroutine; + + Ptr waitingAnimation; + vint waitingGroup = -1; + Group> groupAnimations; + + public: + GuiCoroutineAnimation(const IGuiAnimationCoroutine::Creator& _creator) + :creator(_creator) + { + } + + ~GuiCoroutineAnimation() + { + } + + void OnPlayAndWait(Ptr animation)override + { + CHECK_ERROR(!waitingAnimation && waitingGroup == -1, L"GuiCoroutineAnimation::OnPlayAndWait(Ptr)#Cannot be called when an animation or a group has already been waiting for."); + waitingAnimation = animation; + waitingAnimation->Start(); + } + + void OnPlayInGroup(Ptr animation, vint groupId)override + { + groupAnimations.Add(groupId, animation); + animation->Start(); + } + + void OnWaitForGroup(vint groupId)override + { + CHECK_ERROR(!waitingAnimation && waitingGroup == -1, L"GuiCoroutineAnimation::OnWaitForGroup(vint)#Cannot be called when an animation or a group has already been waiting for."); + if (groupAnimations.Keys().Contains(groupId)) + { + waitingGroup = groupId; + } + } + + void Start()override + { + CHECK_ERROR(!coroutine, L"GuiCoroutineAnimation::Start()#Cannot be called more than once."); + coroutine = creator(this); + } + + void Pause()override + { + if (waitingAnimation) + { + waitingAnimation->Pause(); + } + for (vint i = 0; i < groupAnimations.Count(); i++) + { + FOREACH(Ptr, animation, groupAnimations.GetByIndex(i)) + { + animation->Pause(); + } + } + } + + void Resume()override + { + if (waitingAnimation) + { + waitingAnimation->Resume(); + } + for (vint i = 0; i < groupAnimations.Count(); i++) + { + FOREACH(Ptr, animation, groupAnimations.GetByIndex(i)) + { + animation->Resume(); + } + } + } + + void Run()override + { + CHECK_ERROR(coroutine, L"GuiCoroutineAnimation::Run()#Cannot be called before calling Start."); + + if (waitingAnimation) + { + waitingAnimation->Run(); + if (waitingAnimation->GetStopped()) + { + waitingAnimation = nullptr; + } + } + + for (vint i = groupAnimations.Count() - 1; i >= 0; i--) + { + auto& animations = groupAnimations.GetByIndex(i); + for (vint j = animations.Count() - 1; j >= 0; j--) + { + auto animation = animations[j]; + animation->Run(); + if (animation->GetStopped()) + { + groupAnimations.Remove(i, animation.Obj()); + } + } + } + + if (waitingGroup != -1 && !groupAnimations.Keys().Contains(waitingGroup)) + { + waitingGroup = -1; + } + + if (coroutine->GetStatus() == CoroutineStatus::Waiting) + { + if (waitingAnimation || waitingGroup != -1) + { + return; + } + coroutine->Resume(true, nullptr); + } + } + + bool GetStopped()override + { + if (!coroutine) return false; + if (coroutine->GetStatus() != CoroutineStatus::Stopped) return false; + if (waitingAnimation || groupAnimations.Count() > 0) return false; + return true; + } + }; + + void IGuiAnimationCoroutine::WaitAndPause(IImpl* impl, vuint64_t milliseconds) + { + return PlayAndWaitAndPause(impl, IGuiAnimation::CreateAnimation({}, milliseconds)); + } + + void IGuiAnimationCoroutine::PlayAndWaitAndPause(IImpl* impl, Ptr animation) + { + impl->OnPlayAndWait(animation); + } + + void IGuiAnimationCoroutine::PlayInGroupAndPause(IImpl* impl, Ptr animation, vint groupId) + { + impl->OnPlayInGroup(animation, groupId); + } + + void IGuiAnimationCoroutine::WaitForGroupAndPause(IImpl* impl, vint groupId) + { + impl->OnWaitForGroup(groupId); + } + + void IGuiAnimationCoroutine::ReturnAndExit(IImpl* impl) + { + } + + Ptr IGuiAnimationCoroutine::Create(const Creator& creator) + { + return new GuiCoroutineAnimation(creator); + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEMPLATES\GUICOMMONTEMPLATES.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace templates + { + using namespace elements; + using namespace compositions; + using namespace templates; + using namespace controls; + using namespace theme; + +/*********************************************************************** +GuiCommonDatePickerLook +***********************************************************************/ + + vint GetDayCountForMonth(vint year, vint month) + { + bool isLeapYear = (year % 100 == 0) ? (year % 400 == 0) : (year % 4 == 0); + switch (month) + { + case 1:case 3:case 5:case 7:case 8:case 10:case 12: + return 31; + case 4:case 6:case 9:case 11: + return 30; + default: + return isLeapYear ? 29 : 28; + } + } + + void StepPreviousMonth(vint& year, vint& month) + { + if (month == 1) + { + year--; + month = 12; + } + else + { + month--; + } + } + + void StepNextMonth(vint& year, vint& month) + { + if (month == 12) + { + year++; + month = 1; + } + else + { + month++; + } + } + + void GuiCommonDatePickerLook::SetDay(const DateTime& day, vint& index, bool currentMonth) + { + dateDays[index] = day; + GuiSolidLabelElement* label = labelDays[index]; + label->SetText(itow(day.day)); + label->SetColor(currentMonth ? primaryTextColor : secondaryTextColor); + index++; + } + + void GuiCommonDatePickerLook::comboYearMonth_SelectedIndexChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (!preventComboEvent) + { + if (comboYear->GetSelectedIndex() != -1 && comboMonth->GetSelectedIndex() != -1) + { + vint year = comboYear->GetSelectedIndex() + YearFirst; + vint month = comboMonth->GetSelectedIndex() + 1; + SetDate(DateTime::FromDateTime(year, month, 1)); + + GuiEventArgs arguments(this); + DateChanged.Execute(arguments); + commands->NotifyDateChanged(); + commands->NotifyDateNavigated(); + } + } + } + + void GuiCommonDatePickerLook::buttonDay_SelectedChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (!preventButtonEvent) + { + GuiSelectableButton* button = dynamic_cast(sender->GetRelatedControl()); + if (button->GetSelected()) + { + vint index = buttonDays.IndexOf(button); + if (index != -1) + { + DateTime day = dateDays[index]; + if (day.year != currentDate.year || day.month != currentDate.month) + { + SetDate(day); + } + else + { + currentDate = day; + } + + GuiEventArgs arguments(this); + DateChanged.Execute(arguments); + commands->NotifyDateChanged(); + commands->NotifyDateSelected(); + } + } + } + } + + void GuiCommonDatePickerLook::DisplayMonth(vint year, vint month) + { + if (YearFirst <= year && year <= YearLast && 1 <= month && month <= 12) + { + preventComboEvent = true; + comboYear->SetSelectedIndex(year - YearFirst); + comboMonth->SetSelectedIndex(month - 1); + preventComboEvent = false; + } + + vint yearPrev = year, yearNext = year, monthPrev = month, monthNext = month; + StepPreviousMonth(yearPrev, monthPrev); + StepNextMonth(yearNext, monthNext); + + vint countPrev = GetDayCountForMonth(yearPrev, monthPrev); + vint count = GetDayCountForMonth(year, month); + vint countNext = GetDayCountForMonth(yearNext, monthNext); + + DateTime firstDay = DateTime::FromDateTime(year, month, 1); + vint showPrev = firstDay.dayOfWeek; + if (showPrev == 0) showPrev = DaysOfWeek; + vint show = count; + vint showNext = DaysOfWeek*DayRows - showPrev - show; + + vint index = 0; + for (vint i = 0; i < showPrev; i++) + { + DateTime day = DateTime::FromDateTime(yearPrev, monthPrev, countPrev - (showPrev - i - 1)); + SetDay(day, index, false); + } + for (vint i = 0; i < show; i++) + { + DateTime day = DateTime::FromDateTime(year, month, i + 1); + SetDay(day, index, true); + } + for (vint i = 0; i < showNext; i++) + { + DateTime day = DateTime::FromDateTime(yearNext, monthNext, i + 1); + SetDay(day, index, false); + } + } + + void GuiCommonDatePickerLook::SelectDay(vint day) + { + for (vint i = 0; i < dateDays.Count(); i++) + { + const DateTime& dt = dateDays[i]; + if (dt.year == currentDate.year && dt.month == currentDate.month && dt.day == day) + { + preventButtonEvent = true; + buttonDays[i]->SetSelected(true); + preventButtonEvent = false; + break; + } + } + } + + GuiCommonDatePickerLook::GuiCommonDatePickerLook(Color _backgroundColor, Color _primaryTextColor, Color _secondaryTextColor) + :backgroundColor(_backgroundColor) + , primaryTextColor(_primaryTextColor) + , secondaryTextColor(_secondaryTextColor) + { + DateChanged.SetAssociatedComposition(this); + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + GuiTableComposition* monthTable = 0; + GuiTableComposition* dayTable = 0; + { + listYears = new GuiTextList(theme::ThemeName::TextList); + listYears->SetHorizontalAlwaysVisible(false); + for (vint i = YearFirst; i <= YearLast; i++) + { + listYears->GetItems().Add(new list::TextItem(itow(i))); + } + comboYear = new GuiComboBoxListControl(theme::ThemeName::ComboBox, listYears); + comboYear->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 2, 0)); + comboYear->SelectedIndexChanged.AttachMethod(this, &GuiCommonDatePickerLook::comboYearMonth_SelectedIndexChanged); + } + { + listMonths = new GuiTextList(theme::ThemeName::TextList); + listMonths->SetHorizontalAlwaysVisible(false); + comboMonth = new GuiComboBoxListControl(theme::ThemeName::ComboBox, listMonths); + comboMonth->GetBoundsComposition()->SetAlignmentToParent(Margin(2, 0, 0, 0)); + comboMonth->SelectedIndexChanged.AttachMethod(this, &GuiCommonDatePickerLook::comboYearMonth_SelectedIndexChanged); + } + { + monthTable = new GuiTableComposition; + monthTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); + monthTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + monthTable->SetRowsAndColumns(1, 2); + monthTable->SetRowOption(0, GuiCellOption::MinSizeOption()); + monthTable->SetColumnOption(0, GuiCellOption::PercentageOption(0.5)); + monthTable->SetColumnOption(1, GuiCellOption::PercentageOption(0.5)); + { + GuiCellComposition* cell = new GuiCellComposition; + monthTable->AddChild(cell); + cell->SetSite(0, 0, 1, 1); + cell->AddChild(comboYear->GetBoundsComposition()); + } + { + GuiCellComposition* cell = new GuiCellComposition; + monthTable->AddChild(cell); + cell->SetSite(0, 1, 1, 1); + cell->AddChild(comboMonth->GetBoundsComposition()); + } + } + { + dayTable = new GuiTableComposition; + dayTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + dayTable->SetCellPadding(4); + dayTable->SetRowsAndColumns(DayRows + DayRowStart, DaysOfWeek); + + for (vint i = 0; i < DayRowStart; i++) + { + dayTable->SetRowOption(i, GuiCellOption::MinSizeOption()); + } + for (vint i = 0; i < DayRows; i++) + { + dayTable->SetRowOption(i + DayRowStart, GuiCellOption::PercentageOption(1.0)); + } + for (vint i = 0; i < DaysOfWeek; i++) + { + dayTable->SetColumnOption(i, GuiCellOption::PercentageOption(1.0)); + } + + { + GuiCellComposition* cell = new GuiCellComposition; + dayTable->AddChild(cell); + cell->SetSite(0, 0, 1, DaysOfWeek); + cell->AddChild(monthTable); + } + + labelDaysOfWeek.Resize(7); + for (vint i = 0; i < DaysOfWeek; i++) + { + GuiCellComposition* cell = new GuiCellComposition; + dayTable->AddChild(cell); + cell->SetSite(1, i, 1, 1); + + GuiSolidLabelElement* element = GuiSolidLabelElement::Create(); + element->SetAlignments(Alignment::Center, Alignment::Center); + element->SetColor(primaryTextColor); + labelDaysOfWeek[i] = element; + cell->SetOwnedElement(element); + } + + buttonDays.Resize(DaysOfWeek*DayRows); + labelDays.Resize(DaysOfWeek*DayRows); + dateDays.Resize(DaysOfWeek*DayRows); + + auto dayMutexController = new GuiSelectableButton::MutexGroupController; + AddComponent(dayMutexController); + + for (vint i = 0; i < DaysOfWeek; i++) + { + for (vint j = 0; j < DayRows; j++) + { + GuiCellComposition* cell = new GuiCellComposition; + dayTable->AddChild(cell); + cell->SetSite(j + DayRowStart, i, 1, 1); + + GuiSelectableButton* button = new GuiSelectableButton(theme::ThemeName::CheckBox); + button->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + button->SetGroupController(dayMutexController); + button->SelectedChanged.AttachMethod(this, &GuiCommonDatePickerLook::buttonDay_SelectedChanged); + cell->AddChild(button->GetBoundsComposition()); + buttonDays[j*DaysOfWeek + i] = button; + + GuiSolidLabelElement* element = GuiSolidLabelElement::Create(); + element->SetAlignments(Alignment::Center, Alignment::Center); + element->SetText(L"0"); + labelDays[j*DaysOfWeek + i] = element; + + GuiBoundsComposition* elementBounds = new GuiBoundsComposition; + elementBounds->SetOwnedElement(element); + elementBounds->SetAlignmentToParent(Margin(0, 0, 0, 0)); + elementBounds->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); + button->GetContainerComposition()->AddChild(elementBounds); + } + } + } + { + GuiSolidBackgroundElement* element = GuiSolidBackgroundElement::Create(); + element->SetColor(backgroundColor); + dayTable->SetOwnedElement(element); + } + + dayTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); + AddChild(dayTable); + + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + SetFont(font); + } + + GuiCommonDatePickerLook::~GuiCommonDatePickerLook() + { + FinalizeInstanceRecursively(this); + } + + controls::IDatePickerCommandExecutor* GuiCommonDatePickerLook::GetCommands() + { + return commands; + } + + void GuiCommonDatePickerLook::SetCommands(controls::IDatePickerCommandExecutor* value) + { + commands = value; + } + + TemplateProperty GuiCommonDatePickerLook::GetDateButtonTemplate() + { + return dateButtonTemplate; + } + + void GuiCommonDatePickerLook::SetDateButtonTemplate(const TemplateProperty& value) + { + dateButtonTemplate = value; + FOREACH(GuiSelectableButton*, button, buttonDays) + { + button->SetControlTemplate(value); + } + } + + TemplateProperty GuiCommonDatePickerLook::GetDateTextListTemplate() + { + return dateTextListTemplate; + } + + void GuiCommonDatePickerLook::SetDateTextListTemplate(const TemplateProperty& value) + { + dateTextListTemplate = value; + listYears->SetControlTemplate(value); + listMonths->SetControlTemplate(value); + } + + TemplateProperty GuiCommonDatePickerLook::GetDateComboBoxTemplate() + { + return dateComboBoxTemplate; + } + + void GuiCommonDatePickerLook::SetDateComboBoxTemplate(const TemplateProperty& value) + { + dateComboBoxTemplate = value; + comboYear->SetControlTemplate(value); + comboMonth->SetControlTemplate(value); + } + + const Locale& GuiCommonDatePickerLook::GetDateLocale() + { + return dateLocale; + } + + void GuiCommonDatePickerLook::SetDateLocale(const Locale& value) + { + if (dateLocale != value) + { + dateLocale = value; + for (vint i = 0; i < DaysOfWeek; i++) + { + labelDaysOfWeek[i]->SetText(dateLocale.GetShortDayOfWeekName(i)); + } + + listMonths->GetItems().Clear(); + for (vint i = 1; i <= 12; i++) + { + listMonths->GetItems().Add(new list::TextItem(dateLocale.GetLongMonthName(i))); + } + + SetDate(currentDate); + } + } + + const DateTime& GuiCommonDatePickerLook::GetDate() + { + return currentDate; + } + + void GuiCommonDatePickerLook::SetDate(const DateTime& value) + { + currentDate = value; + DisplayMonth(value.year, value.month); + SelectDay(value.day); + } + + const FontProperties& GuiCommonDatePickerLook::GetFont() + { + return font; + } + + void GuiCommonDatePickerLook::SetFont(const FontProperties& value) + { + if (font != value) + { + font = value; + comboYear->SetFont(value); + listYears->SetFont(value); + comboMonth->SetFont(value); + listMonths->SetFont(value); + FOREACH(GuiSolidLabelElement*, label, From(labelDaysOfWeek).Concat(labelDays)) + { + label->SetFont(value); + } + } + } + +/*********************************************************************** +GuiCommonScrollViewLook +***********************************************************************/ + + void GuiCommonScrollViewLook::UpdateTable() + { + if (horizontalScroll->GetVisible()) + { + tableComposition->SetRowOption(1, GuiCellOption::AbsoluteOption(defaultScrollSize)); + } + else + { + tableComposition->SetRowOption(1, GuiCellOption::AbsoluteOption(0)); + } + + if (verticalScroll->GetVisible()) + { + tableComposition->SetColumnOption(1, GuiCellOption::AbsoluteOption(defaultScrollSize)); + } + else + { + tableComposition->SetColumnOption(1, GuiCellOption::AbsoluteOption(0)); + } + + tableComposition->UpdateCellBounds(); + } + + void GuiCommonScrollViewLook::hScroll_OnVisibleChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + UpdateTable(); + } + + void GuiCommonScrollViewLook::vScroll_OnVisibleChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + UpdateTable(); + } + + GuiCommonScrollViewLook::GuiCommonScrollViewLook(vint _defaultScrollSize) + :defaultScrollSize(_defaultScrollSize) + { + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + horizontalScroll = new GuiScroll(theme::ThemeName::HScroll); + horizontalScroll->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + horizontalScroll->SetEnabled(false); + verticalScroll = new GuiScroll(theme::ThemeName::VScroll); + verticalScroll->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + verticalScroll->SetEnabled(false); + + tableComposition = new GuiTableComposition; + AddChild(tableComposition); + tableComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + tableComposition->SetRowsAndColumns(2, 2); + tableComposition->SetRowOption(0, GuiCellOption::PercentageOption(1.0)); + tableComposition->SetRowOption(1, GuiCellOption::MinSizeOption()); + tableComposition->SetColumnOption(0, GuiCellOption::PercentageOption(1.0)); + tableComposition->SetColumnOption(1, GuiCellOption::MinSizeOption()); + UpdateTable(); + { + GuiCellComposition* cell = new GuiCellComposition; + tableComposition->AddChild(cell); + cell->SetSite(1, 0, 1, 1); + cell->AddChild(horizontalScroll->GetBoundsComposition()); + } + { + GuiCellComposition* cell = new GuiCellComposition; + tableComposition->AddChild(cell); + cell->SetSite(0, 1, 1, 1); + cell->AddChild(verticalScroll->GetBoundsComposition()); + } + + containerCellComposition = new GuiCellComposition; + tableComposition->AddChild(containerCellComposition); + containerCellComposition->SetSite(0, 0, 1, 1); + + containerComposition = new GuiBoundsComposition; + containerComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + containerCellComposition->AddChild(containerComposition); + + horizontalScroll->VisibleChanged.AttachMethod(this, &GuiCommonScrollViewLook::hScroll_OnVisibleChanged); + verticalScroll->VisibleChanged.AttachMethod(this, &GuiCommonScrollViewLook::vScroll_OnVisibleChanged); + UpdateTable(); + } + + GuiCommonScrollViewLook::~GuiCommonScrollViewLook() + { + } + + controls::GuiScroll* GuiCommonScrollViewLook::GetHScroll() + { + return horizontalScroll; + } + + controls::GuiScroll* GuiCommonScrollViewLook::GetVScroll() + { + return verticalScroll; + } + + compositions::GuiGraphicsComposition* GuiCommonScrollViewLook::GetContainerComposition() + { + return containerComposition; + } + + TemplateProperty GuiCommonScrollViewLook::GetHScrollTemplate() + { + return hScrollTemplate; + } + + void GuiCommonScrollViewLook::SetHScrollTemplate(const TemplateProperty& value) + { + hScrollTemplate = value; + horizontalScroll->SetControlTemplate(value); + } + + TemplateProperty GuiCommonScrollViewLook::GetVScrollTemplate() + { + return vScrollTemplate; + } + + void GuiCommonScrollViewLook::SetVScrollTemplate(const TemplateProperty& value) + { + vScrollTemplate = value; + verticalScroll->SetControlTemplate(value); + } + +/*********************************************************************** +GuiCommonScrollBehavior +***********************************************************************/ + + void GuiCommonScrollBehavior::SetScroll(vint totalPixels, vint newOffset) + { + vint totalSize = scrollTemplate->GetTotalSize(); + double ratio = (double)newOffset / totalPixels; + vint newPosition = (vint)round(ratio * totalSize); + + vint offset1 = (vint)round(((double)newPosition / totalSize) * totalPixels); + vint offset2 = (vint)round(((double)(newPosition + 1)) / totalSize * totalPixels); + vint delta1 = offset1 - newOffset; + vint delta2 = offset2 - newOffset; + + if (delta1 < 0) { delta1 = -delta1; } + if (delta2 < 0) { delta2 = -delta2; } + + if (delta1 < delta2) + { + scrollTemplate->GetCommands()->SetPosition(newPosition); + } + else + { + scrollTemplate->GetCommands()->SetPosition(newPosition + 1); + } + } + + void GuiCommonScrollBehavior::AttachHandle(compositions::GuiGraphicsComposition* handle) + { + handle->GetEventReceiver()->leftButtonDown.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs& arguments) + { + if (scrollTemplate->GetVisuallyEnabled()) + { + dragging = true; + location.x = arguments.x; + location.y = arguments.y; + } + }); + + handle->GetEventReceiver()->leftButtonUp.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs&) + { + if (scrollTemplate->GetVisuallyEnabled()) + { + dragging = false; + } + }); + } + + GuiCommonScrollBehavior::GuiCommonScrollBehavior() + { + } + + GuiCommonScrollBehavior::~GuiCommonScrollBehavior() + { + } + + void GuiCommonScrollBehavior::AttachScrollTemplate(GuiScrollTemplate* value) + { + scrollTemplate = value; + } + + void GuiCommonScrollBehavior::AttachDecreaseButton(controls::GuiButton* button) + { + button->Clicked.AttachLambda([=](GuiGraphicsComposition*, GuiEventArgs&) + { + scrollTemplate->GetCommands()->SmallDecrease(); + }); + } + + void GuiCommonScrollBehavior::AttachIncreaseButton(controls::GuiButton* button) + { + button->Clicked.AttachLambda([=](GuiGraphicsComposition*, GuiEventArgs&) + { + scrollTemplate->GetCommands()->SmallIncrease(); + }); + } + + void GuiCommonScrollBehavior::AttachHorizontalScrollHandle(compositions::GuiPartialViewComposition* partialView) + { + partialView->GetParent()->GetEventReceiver()->leftButtonDown.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs& arguments) + { + if (scrollTemplate->GetVisuallyEnabled()) + { + if (arguments.x < partialView->GetBounds().x1) + { + scrollTemplate->GetCommands()->BigDecrease(); + } + else if (arguments.x >= partialView->GetBounds().x2) + { + scrollTemplate->GetCommands()->BigIncrease(); + } + } + }); + + AttachHorizontalTrackerHandle(partialView); + } + + void GuiCommonScrollBehavior::AttachVerticalScrollHandle(compositions::GuiPartialViewComposition* partialView) + { + partialView->GetParent()->GetEventReceiver()->leftButtonDown.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs& arguments) + { + if (scrollTemplate->GetVisuallyEnabled()) + { + if (arguments.y < partialView->GetBounds().y1) + { + scrollTemplate->GetCommands()->BigDecrease(); + } + else if (arguments.y >= partialView->GetBounds().y2) + { + scrollTemplate->GetCommands()->BigIncrease(); + } + } + }); + + AttachVerticalTrackerHandle(partialView); + } + + void GuiCommonScrollBehavior::AttachHorizontalTrackerHandle(compositions::GuiPartialViewComposition* partialView) + { + partialView->GetEventReceiver()->mouseMove.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs& arguments) + { + if (dragging) + { + auto bounds = partialView->GetParent()->GetBounds(); + vint totalPixels = bounds.x2 - bounds.x1; + vint currentOffset = partialView->GetBounds().x1; + vint newOffset = currentOffset + (arguments.x - location.x); + SetScroll(totalPixels, newOffset); + } + }); + + AttachHandle(partialView); + } + + void GuiCommonScrollBehavior::AttachVerticalTrackerHandle(compositions::GuiPartialViewComposition* partialView) + { + partialView->GetEventReceiver()->mouseMove.AttachLambda([=](GuiGraphicsComposition*, GuiMouseEventArgs& arguments) + { + if (dragging) + { + auto bounds = partialView->GetParent()->GetBounds(); + vint totalPixels = bounds.y2 - bounds.y1; + vint currentOffset = partialView->GetBounds().y1; + vint newOffset = currentOffset + (arguments.y - location.y); + SetScroll(totalPixels, newOffset); + } + }); + + AttachHandle(partialView); + } + + vint GuiCommonScrollBehavior::GetHorizontalTrackerHandlerPosition(compositions::GuiBoundsComposition* handle, vint totalSize, vint pageSize, vint position) + { + vint width = handle->GetParent()->GetBounds().Width() - handle->GetBounds().Width(); + vint max = totalSize - pageSize; + return max == 0 ? 0 : width * position / max; + } + + vint GuiCommonScrollBehavior::GetVerticalTrackerHandlerPosition(compositions::GuiBoundsComposition* handle, vint totalSize, vint pageSize, vint position) + { + vint height = handle->GetParent()->GetBounds().Height() - handle->GetBounds().Height(); + vint max = totalSize - pageSize; + return max == 0 ? 0 : height * position / max; + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEMPLATES\GUICONTROLSHARED.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace reflection::description; + using namespace compositions; + +/*********************************************************************** +GuiComponent +***********************************************************************/ + + GuiComponent::GuiComponent() + { + } + + GuiComponent::~GuiComponent() + { + } + + void GuiComponent::Attach(GuiInstanceRootObject* rootObject) + { + } + + void GuiComponent::Detach(GuiInstanceRootObject* rootObject) + { + } + +/*********************************************************************** +GuiInstanceRootObject +***********************************************************************/ + + class RootObjectTimerCallback : public Object, public IGuiGraphicsTimerCallback + { + public: + GuiControlHost* controlHost; + GuiInstanceRootObject* rootObject; + bool alive = true; + + RootObjectTimerCallback(GuiInstanceRootObject* _rootObject, GuiControlHost* _controlHost) + :rootObject(_rootObject) + , controlHost(_controlHost) + { + } + + bool Play()override + { + if (alive) + { + for (vint i = rootObject->runningAnimations.Count() - 1; i >= 0; i--) + { + auto animation = rootObject->runningAnimations[i]; + animation->Run(); + if (animation->GetStopped()) + { + rootObject->runningAnimations.RemoveAt(i); + } + } + + if (rootObject->runningAnimations.Count() == 0) + { + rootObject->UninstallTimerCallback(nullptr); + return false; + } + } + return alive; + } + }; + + void GuiInstanceRootObject::InstallTimerCallback(controls::GuiControlHost* controlHost) + { + if (!timerCallback) + { + timerCallback = new RootObjectTimerCallback(this, controlHost); + controlHost->GetTimerManager()->AddCallback(timerCallback); + } + } + + bool GuiInstanceRootObject::UninstallTimerCallback(controls::GuiControlHost* controlHost) + { + if (timerCallback && timerCallback->controlHost != controlHost) + { + timerCallback->alive = false; + timerCallback = nullptr; + return true; + } + return false; + } + + void GuiInstanceRootObject::OnControlHostForInstanceChanged() + { + auto controlHost = GetControlHostForInstance(); + if (UninstallTimerCallback(controlHost)) + { + FOREACH(Ptr, animation, runningAnimations) + { + animation->Pause(); + } + } + + if (controlHost) + { + InstallTimerCallback(controlHost); + FOREACH(Ptr, animation, runningAnimations) + { + animation->Resume(); + } + StartPendingAnimations(); + } + } + + void GuiInstanceRootObject::StartPendingAnimations() + { + FOREACH(Ptr, animation, pendingAnimations) + { + animation->Start(); + } + + CopyFrom(runningAnimations, pendingAnimations, true); + pendingAnimations.Clear(); + } + + GuiInstanceRootObject::GuiInstanceRootObject() + { + } + + GuiInstanceRootObject::~GuiInstanceRootObject() + { + UninstallTimerCallback(nullptr); + } + + void GuiInstanceRootObject::FinalizeInstance() + { + if (!finalized) + { + finalized = true; + + FOREACH(Ptr, subscription, subscriptions) + { + subscription->Close(); + } + FOREACH(GuiComponent*, component, components) + { + component->Detach(this); + } + + subscriptions.Clear(); + for (vint i = 0; i resolver) + { + resourceResolver = resolver; + } + + Ptr GuiInstanceRootObject::ResolveResource(const WString& protocol, const WString& path, bool ensureExist) + { + Ptr object; + if (resourceResolver) + { + object = resourceResolver->ResolveResource(protocol, path); + } + if (ensureExist && !object) + { + throw ArgumentException(L"Resource \"" + protocol + L"://" + path + L"\" does not exist."); + } + return object; + } + + Ptr GuiInstanceRootObject::AddSubscription(Ptr subscription) + { + CHECK_ERROR(finalized == false, L"GuiInstanceRootObject::AddSubscription(Ptr)#Cannot add subscription after finalizing."); + if (subscriptions.Contains(subscription.Obj())) + { + return nullptr; + } + else + { + subscriptions.Add(subscription); + subscription->Open(); + subscription->Update(); + return subscription; + } + } + + void GuiInstanceRootObject::UpdateSubscriptions() + { + FOREACH(Ptr, subscription, subscriptions) + { + subscription->Update(); + } + } + + bool GuiInstanceRootObject::AddComponent(GuiComponent* component) + { + CHECK_ERROR(finalized == false, L"GuiInstanceRootObject::AddComponent(GuiComponent*)#Cannot add component after finalizing."); + if(components.Contains(component)) + { + return false; + } + else + { + components.Add(component); + component->Attach(this); + return true; + } + } + + bool GuiInstanceRootObject::AddControlHostComponent(GuiControlHost* controlHost) + { + return AddComponent(new GuiObjectComponent(controlHost)); + } + + bool GuiInstanceRootObject::AddAnimation(Ptr animation) + { + CHECK_ERROR(finalized == false, L"GuiInstanceRootObject::AddAnimation(Ptr)#Cannot add animation after finalizing."); + if (runningAnimations.Contains(animation.Obj()) || pendingAnimations.Contains(animation.Obj())) + { + return false; + } + else + { + pendingAnimations.Add(animation); + + if (auto controlHost = GetControlHostForInstance()) + { + InstallTimerCallback(controlHost); + StartPendingAnimations(); + } + return true; + } + } + + bool GuiInstanceRootObject::KillAnimation(Ptr animation) + { + if (!animation) return false; + if (runningAnimations.Contains(animation.Obj())) + { + runningAnimations.Remove(animation.Obj()); + return true; + } + if (pendingAnimations.Contains(animation.Obj())) + { + pendingAnimations.Remove(animation.Obj()); + return true; + } + return false; + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEMPLATES\GUICONTROLTEMPLATES.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace templates + { + using namespace collections; + using namespace controls; + using namespace compositions; + using namespace elements; + +/*********************************************************************** +GuiTemplate +***********************************************************************/ + + GuiTemplate_PROPERTIES(GUI_TEMPLATE_PROPERTY_IMPL) + + controls::GuiControlHost* GuiTemplate::GetControlHostForInstance() + { + return GetRelatedControlHost(); + } + + void GuiTemplate::OnParentLineChanged() + { + GuiBoundsComposition::OnParentLineChanged(); + OnControlHostForInstanceChanged(); + } + + GuiTemplate::GuiTemplate() + { + GuiTemplate_PROPERTIES(GUI_TEMPLATE_PROPERTY_EVENT_INIT) + } + + GuiTemplate::~GuiTemplate() + { + FinalizeInstanceRecursively(this); + } + +/*********************************************************************** +Item GuiListItemTemplate +***********************************************************************/ + + void GuiListItemTemplate::OnInitialize() + { + } + + GuiListItemTemplate_PROPERTIES(GUI_TEMPLATE_PROPERTY_IMPL) + + GuiListItemTemplate::GuiListItemTemplate() + { + GuiListItemTemplate_PROPERTIES(GUI_TEMPLATE_PROPERTY_EVENT_INIT) + } + + GuiListItemTemplate::~GuiListItemTemplate() + { + FinalizeAggregation(); + } + + void GuiListItemTemplate::BeginEditListItem() + { + listControl->GetItemProvider()->PushEditing(); + } + + void GuiListItemTemplate::EndEditListItem() + { + CHECK_ERROR(listControl->GetItemProvider()->PopEditing(), L"GuiListItemTemplate::EndEditListItem()#BeginEditListItem and EndEditListItem calls are not paired."); + } + + void GuiListItemTemplate::Initialize(controls::GuiListControl* _listControl) + { + CHECK_ERROR(listControl == nullptr, L"GuiListItemTemplate::Initialize(GuiListControl*)#This function can only be called once."); + listControl = _listControl; + OnInitialize(); + } + +/*********************************************************************** +Template Declarations +***********************************************************************/ + + GUI_CONTROL_TEMPLATE_DECL(GUI_TEMPLATE_CLASS_IMPL) + GUI_ITEM_TEMPLATE_DECL(GUI_TEMPLATE_CLASS_IMPL) + } + } +} + +/*********************************************************************** +.\CONTROLS\TEMPLATES\GUITHEMESTYLEFACTORY.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace theme + { + using namespace collections; + using namespace controls; + using namespace templates; + + class Theme : public Object, public virtual theme::ITheme + { + public: + Dictionary> templates; + ThemeTemplates* first = nullptr; + ThemeTemplates* last = nullptr; + + bool RegisterTheme(const WString& name, Ptr theme) + { + CHECK_ERROR(theme->previous == nullptr, L"vl::presentation::theme::RegisterTheme(const WString&, Ptr)#Theme object has been registered"); + CHECK_ERROR(theme->next == nullptr, L"vl::presentation::theme::RegisterTheme(const WString&, Ptr)#Theme object has been registered"); + + if (templates.Keys().Contains(name)) + { + return false; + } + templates.Add(name, theme); + + if (last) + { + last->next = theme.Obj(); + } + theme->previous = last; + last = theme.Obj(); + + return true; + } + + Ptr UnregisterTheme(const WString& name) + { + vint index = templates.Keys().IndexOf(name); + if (index == -1) + { + return nullptr; + } + + auto themeTemplates = templates.Values().Get(index); + + if (themeTemplates->previous) + { + themeTemplates->previous->next = themeTemplates->next; + } + else + { + first = themeTemplates->next; + } + + if (themeTemplates->next) + { + themeTemplates->next->previous = themeTemplates->previous; + + } + else + { + last = themeTemplates->previous; + } + + templates.Remove(name); + return themeTemplates; + } + + TemplateProperty CreateStyle(ThemeName themeName)override + { + switch (themeName) + { +#define GUI_DEFINE_ITEM_PROPERTY(TEMPLATE, CONTROL) \ + case ThemeName::CONTROL:\ + {\ + auto current = last;\ + while (current) \ + {\ + if (current->CONTROL)\ + {\ + return current->CONTROL; \ + }\ + current = current->previous;\ + }\ + throw Exception(L"Control template for \"" L ## #CONTROL L"\" is not defined.");\ + }\ + + GUI_CONTROL_TEMPLATE_TYPES(GUI_DEFINE_ITEM_PROPERTY) +#undef GUI_DEFINE_ITEM_PROPERTY + default: + CHECK_FAIL(L"vl::presentation::theme::ITheme::CreateStyle(ThemeName)#Unknown theme name."); + } + } + }; + + controls::GuiControlHost* ThemeTemplates::GetControlHostForInstance() + { + return nullptr; + } + + ThemeTemplates::~ThemeTemplates() + { + FinalizeAggregation(); + } + + Theme* currentTheme = nullptr; + + ITheme* GetCurrentTheme() + { + return currentTheme; + } + + void InitializeTheme() + { + CHECK_ERROR(currentTheme == nullptr, L"vl::presentation::theme::InitializeTheme()#Theme has already been initialized"); + currentTheme = new Theme; + } + + void FinalizeTheme() + { + CHECK_ERROR(currentTheme != nullptr, L"vl::presentation::theme::FinalizeTheme()#Theme has not been initialized"); + delete currentTheme; + currentTheme = nullptr; + } + + bool RegisterTheme(Ptr theme) + { + CHECK_ERROR(currentTheme != nullptr, L"vl::presentation::theme::RegisterTheme(const WString&, Ptr)#Theme has already been initialized"); + return currentTheme->RegisterTheme(theme->Name, theme); + } + + Ptr UnregisterTheme(const WString& name) + { + CHECK_ERROR(currentTheme != nullptr, L"vl::presentation::theme::UnregisterTheme(const WString&)#Theme has already been initialized"); + return currentTheme->UnregisterTheme(name); + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEXTEDITORPACKAGE\GUIDOCUMENTVIEWER.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace elements; + using namespace compositions; + +/*********************************************************************** +GuiDocumentItem +***********************************************************************/ + + GuiDocumentItem::GuiDocumentItem(const WString& _name) + :name(_name) + { + container = new GuiBoundsComposition; + container->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + container->SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::Arrow)); + } + + GuiDocumentItem::~GuiDocumentItem() + { + if (!owned) + { + SafeDeleteComposition(container); + } + } + + compositions::GuiGraphicsComposition* GuiDocumentItem::GetContainer() + { + return container; + } + + WString GuiDocumentItem::GetName() + { + return name; + } + +/*********************************************************************** +GuiDocumentCommonInterface +***********************************************************************/ + + void GuiDocumentCommonInterface::InvokeUndoRedoChanged() + { + UndoRedoChanged.Execute(documentControl->GetNotifyEventArguments()); + } + + void GuiDocumentCommonInterface::InvokeModifiedChanged() + { + ModifiedChanged.Execute(documentControl->GetNotifyEventArguments()); + } + + void GuiDocumentCommonInterface::UpdateCaretPoint() + { + GuiGraphicsHost* host=documentComposition->GetRelatedGraphicsHost(); + if(host) + { + Rect caret=documentElement->GetCaretBounds(documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide()); + Point view=GetDocumentViewPosition(); + vint x=caret.x1-view.x; + vint y=caret.y2-view.y; + host->SetCaretPoint(Point(x, y), documentComposition); + } + } + + void GuiDocumentCommonInterface::Move(TextPos caret, bool shift, bool frontSide) + { + TextPos begin=documentElement->GetCaretBegin(); + TextPos end=documentElement->GetCaretEnd(); + + TextPos newBegin=shift?begin:caret; + TextPos newEnd=caret; + documentElement->SetCaret(newBegin, newEnd, frontSide); + documentElement->SetCaretVisible(true); + + Rect bounds=documentElement->GetCaretBounds(newEnd, frontSide); + if(bounds!=Rect()) + { + bounds.x1-=15; + bounds.y1-=15; + bounds.x2+=15; + bounds.y2+=15; + EnsureRectVisible(bounds); + } + UpdateCaretPoint(); + SelectionChanged.Execute(documentControl->GetNotifyEventArguments()); + } + + bool GuiDocumentCommonInterface::ProcessKey(vint code, bool shift, bool ctrl) + { + if(IGuiShortcutKeyItem* item=internalShortcutKeyManager->TryGetShortcut(ctrl, shift, false, code)) + { + GuiEventArgs arguments; + item->Executed.Execute(arguments); + return true; + } + + TextPos currentCaret=documentElement->GetCaretEnd(); + bool frontSide=documentElement->IsCaretEndPreferFrontSide(); + TextPos begin=documentElement->GetCaretBegin(); + TextPos end=documentElement->GetCaretEnd(); + + switch(code) + { + case VKEY_UP: + { + TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretMoveUp, frontSide); + Move(newCaret, shift, frontSide); + } + break; + case VKEY_DOWN: + { + TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretMoveDown, frontSide); + Move(newCaret, shift, frontSide); + } + break; + case VKEY_LEFT: + { + TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretMoveLeft, frontSide); + Move(newCaret, shift, frontSide); + } + break; + case VKEY_RIGHT: + { + TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretMoveRight, frontSide); + Move(newCaret, shift, frontSide); + } + break; + case VKEY_HOME: + { + TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretLineFirst, frontSide); + if(newCaret==currentCaret) + { + newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretFirst, frontSide); + } + Move(newCaret, shift, frontSide); + } + break; + case VKEY_END: + { + TextPos newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretLineLast, frontSide); + if(newCaret==currentCaret) + { + newCaret=documentElement->CalculateCaret(currentCaret, IGuiGraphicsParagraph::CaretLast, frontSide); + } + Move(newCaret, shift, frontSide); + } + break; + case VKEY_PRIOR: + { + } + break; + case VKEY_NEXT: + { + } + break; + case VKEY_BACK: + if(editMode==Editable) + { + if(begin==end) + { + ProcessKey(VKEY_LEFT, true, false); + } + Array text; + EditText(documentElement->GetCaretBegin(), documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide(), text); + return true; + } + break; + case VKEY_DELETE: + if(editMode==Editable) + { + if(begin==end) + { + ProcessKey(VKEY_RIGHT, true, false); + } + Array text; + EditText(documentElement->GetCaretBegin(), documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide(), text); + return true; + } + break; + case VKEY_RETURN: + if(editMode==Editable) + { + if(ctrl) + { + Array text(1); + text[0]=L"\r\n"; + EditText(documentElement->GetCaretBegin(), documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide(), text); + } + else + { + Array text(2); + EditText(documentElement->GetCaretBegin(), documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide(), text); + } + return true; + } + break; + } + return false; + } + + void GuiDocumentCommonInterface::InstallDocumentViewer( + GuiControl* _sender, + compositions::GuiGraphicsComposition* _container, + compositions::GuiGraphicsComposition* eventComposition, + compositions::GuiGraphicsComposition* focusableComposition + ) + { + documentControl=_sender; + + documentElement=GuiDocumentElement::Create(); + documentElement->SetCallback(this); + + documentComposition=new GuiBoundsComposition; + documentComposition->SetOwnedElement(documentElement); + documentComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElement); + documentComposition->SetAlignmentToParent(Margin(5, 5, 5, 5)); + _container->AddChild(documentComposition); + + documentComposition->GetEventReceiver()->mouseMove.AttachMethod(this, &GuiDocumentCommonInterface::OnMouseMove); + documentComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiDocumentCommonInterface::OnMouseDown); + documentComposition->GetEventReceiver()->leftButtonUp.AttachMethod(this, &GuiDocumentCommonInterface::OnMouseUp); + documentComposition->GetEventReceiver()->mouseLeave.AttachMethod(this, &GuiDocumentCommonInterface::OnMouseLeave); + + _sender->FontChanged.AttachMethod(this, &GuiDocumentCommonInterface::OnFontChanged); + focusableComposition->GetEventReceiver()->caretNotify.AttachMethod(this, &GuiDocumentCommonInterface::OnCaretNotify); + focusableComposition->GetEventReceiver()->gotFocus.AttachMethod(this, &GuiDocumentCommonInterface::OnGotFocus); + focusableComposition->GetEventReceiver()->lostFocus.AttachMethod(this, &GuiDocumentCommonInterface::OnLostFocus); + focusableComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiDocumentCommonInterface::OnKeyDown); + focusableComposition->GetEventReceiver()->charInput.AttachMethod(this, &GuiDocumentCommonInterface::OnCharInput); + + undoRedoProcessor->Setup(documentElement, documentComposition); + ActiveHyperlinkChanged.SetAssociatedComposition(eventComposition); + ActiveHyperlinkExecuted.SetAssociatedComposition(eventComposition); + SelectionChanged.SetAssociatedComposition(eventComposition); + UndoRedoChanged.SetAssociatedComposition(eventComposition); + ModifiedChanged.SetAssociatedComposition(eventComposition); + + undoRedoProcessor->UndoRedoChanged.Add(this, &GuiDocumentCommonInterface::InvokeUndoRedoChanged); + undoRedoProcessor->ModifiedChanged.Add(this, &GuiDocumentCommonInterface::InvokeModifiedChanged); + SetDocument(new DocumentModel); + } + + void GuiDocumentCommonInterface::SetActiveHyperlink(Ptr package) + { + ActivateActiveHyperlink(false); + activeHyperlinks = + !package ? nullptr : + package->hyperlinks.Count() == 0 ? nullptr : + package; + ActivateActiveHyperlink(true); + ActiveHyperlinkChanged.Execute(documentControl->GetNotifyEventArguments()); + } + + void GuiDocumentCommonInterface::ActivateActiveHyperlink(bool activate) + { + if (activeHyperlinks) + { + FOREACH(Ptr, run, activeHyperlinks->hyperlinks) + { + run->styleName = activate ? run->activeStyleName : run->normalStyleName; + } + documentElement->NotifyParagraphUpdated(activeHyperlinks->row, 1, 1, false); + } + } + + void GuiDocumentCommonInterface::AddShortcutCommand(vint key, const Func& eventHandler) + { + IGuiShortcutKeyItem* item=internalShortcutKeyManager->CreateShortcut(true, false, false, key); + item->Executed.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) + { + eventHandler(); + }); + } + + void GuiDocumentCommonInterface::EditTextInternal(TextPos begin, TextPos end, const Func& editor) + { + // save run before editing + if(begin>end) + { + TextPos temp=begin; + begin=end; + end=temp; + } + Ptr originalModel=documentElement->GetDocument()->CopyDocument(begin, end, true); + if(originalModel) + { + // edit + vint paragraphCount=0; + vint lastParagraphLength=0; + editor(begin, end, paragraphCount, lastParagraphLength); + + // calculate new caret + TextPos caret; + if(paragraphCount==0) + { + caret=begin; + } + else if(paragraphCount==1) + { + caret=TextPos(begin.row, begin.column+lastParagraphLength); + } + else + { + caret=TextPos(begin.row+paragraphCount-1, lastParagraphLength); + } + documentElement->SetCaret(caret, caret, true); + documentControl->TextChanged.Execute(documentControl->GetNotifyEventArguments()); + UpdateCaretPoint(); + SelectionChanged.Execute(documentControl->GetNotifyEventArguments()); + + // save run after editing + Ptr inputModel=documentElement->GetDocument()->CopyDocument(begin, caret, true); + + // submit redo-undo + GuiDocumentUndoRedoProcessor::ReplaceModelStruct arguments; + arguments.originalStart=begin; + arguments.originalEnd=end; + arguments.originalModel=originalModel; + arguments.inputStart=begin; + arguments.inputEnd=caret; + arguments.inputModel=inputModel; + undoRedoProcessor->OnReplaceModel(arguments); + } + } + + void GuiDocumentCommonInterface::EditStyleInternal(TextPos begin, TextPos end, const Func& editor) + { + // save run before editing + if(begin>end) + { + TextPos temp=begin; + begin=end; + end=temp; + } + Ptr originalModel=documentElement->GetDocument()->CopyDocument(begin, end, true); + if(originalModel) + { + // edit + editor(begin, end); + + // save run after editing + Ptr inputModel=documentElement->GetDocument()->CopyDocument(begin, end, true); + + // submit redo-undo + GuiDocumentUndoRedoProcessor::ReplaceModelStruct arguments; + arguments.originalStart=begin; + arguments.originalEnd=end; + arguments.originalModel=originalModel; + arguments.inputStart=begin; + arguments.inputEnd=end; + arguments.inputModel=inputModel; + undoRedoProcessor->OnReplaceModel(arguments); + } + } + + void GuiDocumentCommonInterface::MergeBaselineAndDefaultFont(Ptr document) + { + document->MergeDefaultFont(documentControl->GetFont()); + if (baselineDocument) + { + document->MergeBaselineStyles(baselineDocument); + } + } + + void GuiDocumentCommonInterface::OnFontChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + auto document = documentElement->GetDocument(); + MergeBaselineAndDefaultFont(document); + documentElement->SetDocument(document); + } + + void GuiDocumentCommonInterface::OnCaretNotify(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(documentControl->GetVisuallyEnabled()) + { + if(editMode!=ViewOnly) + { + documentElement->SetCaretVisible(!documentElement->GetCaretVisible()); + } + } + } + + void GuiDocumentCommonInterface::OnGotFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(documentControl->GetVisuallyEnabled()) + { + if(editMode!=ViewOnly) + { + documentElement->SetCaretVisible(true); + UpdateCaretPoint(); + } + } + } + + void GuiDocumentCommonInterface::OnLostFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(documentControl->GetVisuallyEnabled()) + { + documentElement->SetCaretVisible(false); + } + } + + void GuiDocumentCommonInterface::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) + { + if(documentControl->GetVisuallyEnabled()) + { + if(editMode!=ViewOnly) + { + if(ProcessKey(arguments.code, arguments.shift, arguments.ctrl)) + { + arguments.handled=true; + } + } + } + } + + void GuiDocumentCommonInterface::OnCharInput(compositions::GuiGraphicsComposition* sender, compositions::GuiCharEventArgs& arguments) + { + if(documentControl->GetVisuallyEnabled()) + { + if(editMode==Editable && arguments.code!=VKEY_ESCAPE && arguments.code!=VKEY_BACK && arguments.code!=VKEY_RETURN && !arguments.ctrl) + { + Array text(1); + text[0]=WString(arguments.code); + EditText(documentElement->GetCaretBegin(), documentElement->GetCaretEnd(), documentElement->IsCaretEndPreferFrontSide(), text); + } + } + } + + void GuiDocumentCommonInterface::OnMouseMove(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(documentControl->GetVisuallyEnabled()) + { + switch(editMode) + { + case ViewOnly: + { + auto package = documentElement->GetHyperlinkFromPoint({ arguments.x, arguments.y }); + bool handCursor = false; + + if(dragging) + { + if(activeHyperlinks) + { + if (package && CompareEnumerable(activeHyperlinks->hyperlinks, package->hyperlinks) == 0) + { + ActivateActiveHyperlink(true); + handCursor = true; + } + else + { + ActivateActiveHyperlink(false); + } + } + } + else + { + SetActiveHyperlink(package); + handCursor = activeHyperlinks; + } + + if(handCursor) + { + auto cursor = GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::Hand); + documentComposition->SetAssociatedCursor(cursor); + } + else + { + documentComposition->SetAssociatedCursor(nullptr); + } + } + break; + case Selectable: + case Editable: + if(dragging) + { + TextPos caret=documentElement->CalculateCaretFromPoint(Point(arguments.x, arguments.y)); + TextPos oldCaret=documentElement->GetCaretBegin(); + Move(caret, true, (oldCaret==caret?documentElement->IsCaretEndPreferFrontSide():caretGetVisuallyEnabled()) + { + documentControl->SetFocus(); + switch(editMode) + { + case ViewOnly: + SetActiveHyperlink(documentElement->GetHyperlinkFromPoint({ arguments.x, arguments.y })); + break; + case Selectable: + case Editable: + { + TextPos caret=documentElement->CalculateCaretFromPoint(Point(arguments.x, arguments.y)); + TextPos oldCaret=documentElement->GetCaretEnd(); + if(caret!=oldCaret) + { + Move(caret, arguments.shift, caretGetVisuallyEnabled()) + { + dragging=false; + switch(editMode) + { + case ViewOnly: + { + auto package = documentElement->GetHyperlinkFromPoint({ arguments.x, arguments.y }); + if(activeHyperlinks) + { + if (package && CompareEnumerable(activeHyperlinks->hyperlinks, package->hyperlinks) == 0) + { + ActiveHyperlinkExecuted.Execute(documentControl->GetNotifyEventArguments()); + } + else + { + SetActiveHyperlink(nullptr); + } + } + } + break; + default:; + } + } + } + + void GuiDocumentCommonInterface::OnMouseLeave(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + SetActiveHyperlink(0); + } + + Point GuiDocumentCommonInterface::GetDocumentViewPosition() + { + return Point(0, 0); + } + + void GuiDocumentCommonInterface::EnsureRectVisible(Rect bounds) + { + } + + //================ callback + + void GuiDocumentCommonInterface::OnStartRender() + { + FOREACH(Ptr, item, documentItems.Values()) + { + item->visible = false; + } + } + + void GuiDocumentCommonInterface::OnFinishRender() + { + FOREACH(Ptr, item, documentItems.Values()) + { + if (item->container->GetVisible() != item->visible) + { + item->container->SetVisible(item->visible); + } + } + } + + Size GuiDocumentCommonInterface::OnRenderEmbeddedObject(const WString& name, const Rect& location) + { + vint index = documentItems.Keys().IndexOf(name); + if (index != -1) + { + auto item = documentItems.Values()[index]; + auto size = item->container->GetBounds().GetSize(); + item->container->SetBounds(Rect(location.LeftTop(), Size(0, 0))); + item->visible = true; + return size; + } + return Size(); + } + + //================ basic + + GuiDocumentCommonInterface::GuiDocumentCommonInterface() + { + undoRedoProcessor=new GuiDocumentUndoRedoProcessor; + + internalShortcutKeyManager=new GuiShortcutKeyManager; + AddShortcutCommand('Z', Func(this, &GuiDocumentCommonInterface::Undo)); + AddShortcutCommand('Y', Func(this, &GuiDocumentCommonInterface::Redo)); + AddShortcutCommand('A', Func(this, &GuiDocumentCommonInterface::SelectAll)); + AddShortcutCommand('X', Func(this, &GuiDocumentCommonInterface::Cut)); + AddShortcutCommand('C', Func(this, &GuiDocumentCommonInterface::Copy)); + AddShortcutCommand('V', Func(this, &GuiDocumentCommonInterface::Paste)); + } + + GuiDocumentCommonInterface::~GuiDocumentCommonInterface() + { + } + + Ptr GuiDocumentCommonInterface::GetDocument() + { + return documentElement->GetDocument(); + } + + void GuiDocumentCommonInterface::SetDocument(Ptr value) + { + SetActiveHyperlink(0); + ClearUndoRedo(); + NotifyModificationSaved(); + + if (value) + { + if (value->paragraphs.Count() == 0) + { + value->paragraphs.Add(new DocumentParagraphRun); + } + MergeBaselineAndDefaultFont(value); + } + + documentElement->SetDocument(value); + } + + //================ document items + + bool GuiDocumentCommonInterface::AddDocumentItem(Ptr value) + { + if (documentItems.Keys().Contains(value->GetName())) + { + return false; + } + documentItems.Add(value->GetName(), value); + documentComposition->AddChild(value->container); + value->visible = false; + value->owned = true; + value->container->SetVisible(false); + return true; + } + + bool GuiDocumentCommonInterface::RemoveDocumentItem(Ptr value) + { + vint index = documentItems.Keys().IndexOf(value->GetName()); + if (index == -1) + { + return false; + } + if (documentItems.Values()[index] != value) + { + return false; + } + value->owned = false; + documentComposition->RemoveChild(value->container); + documentItems.Remove(value->GetName()); + return true; + } + + const GuiDocumentCommonInterface::DocumentItemMap& GuiDocumentCommonInterface::GetDocumentItems() + { + return documentItems; + } + + //================ caret operations + + TextPos GuiDocumentCommonInterface::GetCaretBegin() + { + return documentElement->GetCaretBegin(); + } + + TextPos GuiDocumentCommonInterface::GetCaretEnd() + { + return documentElement->GetCaretEnd(); + } + + void GuiDocumentCommonInterface::SetCaret(TextPos begin, TextPos end) + { + documentElement->SetCaret(begin, end, end>=begin); + UpdateCaretPoint(); + SelectionChanged.Execute(documentControl->GetNotifyEventArguments()); + } + + TextPos GuiDocumentCommonInterface::CalculateCaretFromPoint(Point point) + { + return documentElement->CalculateCaretFromPoint(point); + } + + Rect GuiDocumentCommonInterface::GetCaretBounds(TextPos caret, bool frontSide) + { + return documentElement->GetCaretBounds(caret, frontSide); + } + + //================ editing operations + + void GuiDocumentCommonInterface::NotifyParagraphUpdated(vint index, vint oldCount, vint newCount, bool updatedText) + { + documentElement->NotifyParagraphUpdated(index, oldCount, newCount, updatedText); + } + + void GuiDocumentCommonInterface::EditRun(TextPos begin, TextPos end, Ptr model, bool copy) + { + EditTextInternal(begin, end, [=](TextPos begin, TextPos end, vint& paragraphCount, vint& lastParagraphLength) + { + documentElement->EditRun(begin, end, model, copy); + paragraphCount=model->paragraphs.Count(); + lastParagraphLength=paragraphCount==0?0:model->paragraphs[paragraphCount-1]->GetText(false).Length(); + }); + } + + void GuiDocumentCommonInterface::EditText(TextPos begin, TextPos end, bool frontSide, const collections::Array& text) + { + EditTextInternal(begin, end, [=, &text](TextPos begin, TextPos end, vint& paragraphCount, vint& lastParagraphLength) + { + documentElement->EditText(begin, end, frontSide, text); + paragraphCount=text.Count(); + lastParagraphLength=paragraphCount==0?0:text[paragraphCount-1].Length(); + }); + } + + void GuiDocumentCommonInterface::EditStyle(TextPos begin, TextPos end, Ptr style) + { + EditStyleInternal(begin, end, [=](TextPos begin, TextPos end) + { + documentElement->EditStyle(begin, end, style); + }); + } + + void GuiDocumentCommonInterface::EditImage(TextPos begin, TextPos end, Ptr image) + { + EditTextInternal(begin, end, [=](TextPos begin, TextPos end, vint& paragraphCount, vint& lastParagraphLength) + { + documentElement->EditImage(begin, end, image); + paragraphCount=1; + lastParagraphLength=wcslen(DocumentImageRun::RepresentationText); + }); + } + + void GuiDocumentCommonInterface::EditHyperlink(vint paragraphIndex, vint begin, vint end, const WString& reference, const WString& normalStyleName, const WString& activeStyleName) + { + EditStyleInternal(TextPos(paragraphIndex, begin), TextPos(paragraphIndex, end), [=](TextPos begin, TextPos end) + { + documentElement->EditHyperlink(begin.row, begin.column, end.column, reference, normalStyleName, activeStyleName); + }); + } + + void GuiDocumentCommonInterface::RemoveHyperlink(vint paragraphIndex, vint begin, vint end) + { + EditStyleInternal(TextPos(paragraphIndex, begin), TextPos(paragraphIndex, end), [=](TextPos begin, TextPos end) + { + documentElement->RemoveHyperlink(begin.row, begin.column, end.column); + }); + } + + void GuiDocumentCommonInterface::EditStyleName(TextPos begin, TextPos end, const WString& styleName) + { + EditStyleInternal(begin, end, [=](TextPos begin, TextPos end) + { + documentElement->EditStyleName(begin, end, styleName); + }); + } + + void GuiDocumentCommonInterface::RemoveStyleName(TextPos begin, TextPos end) + { + EditStyleInternal(begin, end, [=](TextPos begin, TextPos end) + { + documentElement->RemoveStyleName(begin, end); + }); + } + + void GuiDocumentCommonInterface::RenameStyle(const WString& oldStyleName, const WString& newStyleName) + { + documentElement->RenameStyle(oldStyleName, newStyleName); + + // submit redo-undo + GuiDocumentUndoRedoProcessor::RenameStyleStruct arguments; + arguments.oldStyleName=oldStyleName; + arguments.newStyleName=newStyleName; + undoRedoProcessor->OnRenameStyle(arguments); + } + + void GuiDocumentCommonInterface::ClearStyle(TextPos begin, TextPos end) + { + EditStyleInternal(begin, end, [=](TextPos begin, TextPos end) + { + documentElement->ClearStyle(begin, end); + }); + } + + Ptr GuiDocumentCommonInterface::SummarizeStyle(TextPos begin, TextPos end) + { + if (begin>end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + return documentElement->SummarizeStyle(begin, end); + } + + Nullable GuiDocumentCommonInterface::SummarizeStyleName(TextPos begin, TextPos end) + { + if (begin>end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + return documentElement->SummarizeStyleName(begin, end); + } + + void GuiDocumentCommonInterface::SetParagraphAlignments(TextPos begin, TextPos end, const collections::Array>& alignments) + { + vint first = begin.row; + vint last = end.row; + if (first > last) + { + vint temp = first; + first = last; + last = temp; + } + + Ptr document = documentElement->GetDocument(); + if (0 <= first && first < document->paragraphs.Count() && 0 <= last && last < document->paragraphs.Count() && last - first + 1 == alignments.Count()) + { + Ptr arguments = new GuiDocumentUndoRedoProcessor::SetAlignmentStruct; + arguments->start = first; + arguments->end = last; + arguments->originalAlignments.Resize(alignments.Count()); + arguments->inputAlignments.Resize(alignments.Count()); + for (vint i = first; i <= last; i++) + { + arguments->originalAlignments[i - first] = document->paragraphs[i]->alignment; + arguments->inputAlignments[i - first] = alignments[i - first]; + } + documentElement->SetParagraphAlignment(begin, end, alignments); + undoRedoProcessor->OnSetAlignment(arguments); + } + } + + void GuiDocumentCommonInterface::SetParagraphAlignment(TextPos begin, TextPos end, Nullable alignment) + { +#if defined VCZH_GCC && defined VCZH_64 +#define abs labs +#endif + Array> alignments(abs(begin.row - end.row) + 1); +#if defined VCZH_GCC && defined VCZH_64 +#undef abs +#endif + for (vint i = 0; i < alignments.Count(); i++) + { + alignments[i] = alignment; + } + SetParagraphAlignments(begin, end, alignments); + } + + Nullable GuiDocumentCommonInterface::SummarizeParagraphAlignment(TextPos begin, TextPos end) + { + if (begin>end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + return documentElement->SummarizeParagraphAlignment(begin, end); + } + + //================ editing control + + WString GuiDocumentCommonInterface::GetActiveHyperlinkReference() + { + return activeHyperlinks ? activeHyperlinks->hyperlinks[0]->reference : L""; + } + + GuiDocumentCommonInterface::EditMode GuiDocumentCommonInterface::GetEditMode() + { + return editMode; + } + + void GuiDocumentCommonInterface::SetEditMode(EditMode value) + { + if(activeHyperlinks) + { + SetActiveHyperlink(nullptr); + } + + editMode=value; + if(editMode==ViewOnly) + { + documentComposition->SetAssociatedCursor(0); + } + else + { + INativeCursor* cursor=GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::IBeam); + documentComposition->SetAssociatedCursor(cursor); + } + } + + //================ selection operations + + void GuiDocumentCommonInterface::SelectAll() + { + vint lastIndex=documentElement->GetDocument()->paragraphs.Count()-1; + Ptr lastParagraph=documentElement->GetDocument()->paragraphs[lastIndex]; + + TextPos begin(0, 0); + TextPos end(lastIndex, lastParagraph->GetText(false).Length()); + SetCaret(begin, end); + } + + WString GuiDocumentCommonInterface::GetSelectionText() + { + TextPos begin=documentElement->GetCaretBegin(); + TextPos end=documentElement->GetCaretEnd(); + if(begin>end) + { + TextPos temp=begin; + begin=end; + end=temp; + } + + Ptr model=documentElement->GetDocument()->CopyDocument(begin, end, false); + return model->GetText(true); + } + + void GuiDocumentCommonInterface::SetSelectionText(const WString& value) + { + List paragraphs; + { + stream::StringReader reader(value); + WString paragraph; + bool empty=true; + + while(!reader.IsEnd()) + { + WString line=reader.ReadLine(); + if(empty) + { + paragraph+=line; + empty=false; + } + else if(line!=L"") + { + paragraph+=L"\r\n"+line; + } + else + { + paragraphs.Add(paragraph); + paragraph=L""; + empty=true; + } + } + + if(!empty) + { + paragraphs.Add(paragraph); + } + } + + TextPos begin=documentElement->GetCaretBegin(); + TextPos end=documentElement->GetCaretEnd(); + if(begin>end) + { + TextPos temp=begin; + begin=end; + end=temp; + } + + Array text; + CopyFrom(text, paragraphs); + EditText(begin, end, documentElement->IsCaretEndPreferFrontSide(), text); + } + + Ptr GuiDocumentCommonInterface::GetSelectionModel() + { + TextPos begin=documentElement->GetCaretBegin(); + TextPos end=documentElement->GetCaretEnd(); + if(begin>end) + { + TextPos temp=begin; + begin=end; + end=temp; + } + + Ptr model=documentElement->GetDocument()->CopyDocument(begin, end, true); + return model; + } + + void GuiDocumentCommonInterface::SetSelectionModel(Ptr value) + { + TextPos begin=documentElement->GetCaretBegin(); + TextPos end=documentElement->GetCaretEnd(); + if(begin>end) + { + TextPos temp=begin; + begin=end; + end=temp; + } + + EditRun(begin, end, value, true); + } + + //================ clipboard operations + + bool GuiDocumentCommonInterface::CanCut() + { + return editMode==Editable && documentElement->GetCaretBegin()!=documentElement->GetCaretEnd(); + } + + bool GuiDocumentCommonInterface::CanCopy() + { + return documentElement->GetCaretBegin()!=documentElement->GetCaretEnd(); + } + + bool GuiDocumentCommonInterface::CanPaste() + { + if (editMode == Editable) + { + auto reader = GetCurrentController()->ClipboardService()->ReadClipboard(); + return reader->ContainsText() || reader->ContainsDocument() || reader->ContainsImage(); + } + return false; + } + + bool GuiDocumentCommonInterface::Cut() + { + if (!CanCut())return false; + auto writer = GetCurrentController()->ClipboardService()->WriteClipboard(); + auto model = GetSelectionModel(); + writer->SetDocument(model); + writer->Submit(); + SetSelectionText(L""); + return true; + } + + bool GuiDocumentCommonInterface::Copy() + { + if (!CanCopy()) return false; + auto writer = GetCurrentController()->ClipboardService()->WriteClipboard(); + auto model = GetSelectionModel(); + writer->SetDocument(model); + writer->Submit(); + return true; + } + + bool GuiDocumentCommonInterface::Paste() + { + if (!CanPaste()) return false; + auto reader = GetCurrentController()->ClipboardService()->ReadClipboard(); + if (reader->ContainsDocument()) + { + if (auto document = reader->GetDocument()) + { + SetSelectionModel(document); + return true; + } + } + if (reader->ContainsText()) + { + SetSelectionText(reader->GetText()); + return true; + } + if (reader->ContainsImage()) + { + if (auto image = reader->GetImage()) + { + auto imageData = MakePtr(image, 0); + EditImage(GetCaretBegin(), GetCaretEnd(), imageData); + return true; + } + } + return false; + } + + //================ undo redo control + + bool GuiDocumentCommonInterface::CanUndo() + { + return editMode==Editable && undoRedoProcessor->CanUndo(); + } + + bool GuiDocumentCommonInterface::CanRedo() + { + return editMode==Editable && undoRedoProcessor->CanRedo(); + } + + void GuiDocumentCommonInterface::ClearUndoRedo() + { + undoRedoProcessor->ClearUndoRedo(); + } + + bool GuiDocumentCommonInterface::GetModified() + { + return undoRedoProcessor->GetModified(); + } + + void GuiDocumentCommonInterface::NotifyModificationSaved() + { + undoRedoProcessor->NotifyModificationSaved(); + } + + bool GuiDocumentCommonInterface::Undo() + { + if(CanUndo()) + { + return undoRedoProcessor->Undo(); + } + else + { + return false; + } + } + + bool GuiDocumentCommonInterface::Redo() + { + if(CanRedo()) + { + return undoRedoProcessor->Redo(); + } + else + { + return false; + } + } + +/*********************************************************************** +GuiDocumentViewer +***********************************************************************/ + + void GuiDocumentViewer::BeforeControlTemplateUninstalled_() + { + } + + void GuiDocumentViewer::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + baselineDocument = ct->GetBaselineDocument(); + if (documentElement) + { + documentElement->SetCaretColor(ct->GetCaretColor()); + SetDocument(GetDocument()); + } + } + + Point GuiDocumentViewer::GetDocumentViewPosition() + { + return GetViewBounds().LeftTop(); + } + + void GuiDocumentViewer::EnsureRectVisible(Rect bounds) + { + Rect viewBounds=GetViewBounds(); + vint offset=0; + if(bounds.y1viewBounds.y2) + { + offset=bounds.y2-viewBounds.y2; + } + + if (auto scroll = GetVerticalScroll()) + { + scroll->SetPosition(viewBounds.y1 + offset); + } + } + + GuiDocumentViewer::GuiDocumentViewer(theme::ThemeName themeName) + :GuiScrollContainer(themeName) + { + SetFocusableComposition(boundsComposition); + InstallDocumentViewer(this, containerComposition, boundsComposition, focusableComposition); + + SetExtendToFullWidth(true); + SetHorizontalAlwaysVisible(false); + } + + GuiDocumentViewer::~GuiDocumentViewer() + { + } + + const WString& GuiDocumentViewer::GetText() + { + text=documentElement->GetDocument()->GetText(true); + return text; + } + + void GuiDocumentViewer::SetText(const WString& value) + { + SelectAll(); + SetSelectionText(value); + } + +/*********************************************************************** +GuiDocumentLabel +***********************************************************************/ + + void GuiDocumentLabel::BeforeControlTemplateUninstalled_() + { + } + + void GuiDocumentLabel::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + baselineDocument = ct->GetBaselineDocument(); + if (documentElement) + { + documentElement->SetCaretColor(ct->GetCaretColor()); + SetDocument(GetDocument()); + } + } + + GuiDocumentLabel::GuiDocumentLabel(theme::ThemeName themeName) + :GuiControl(themeName) + { + SetFocusableComposition(boundsComposition); + InstallDocumentViewer(this, containerComposition, boundsComposition, focusableComposition); + } + + GuiDocumentLabel::~GuiDocumentLabel() + { + } + + const WString& GuiDocumentLabel::GetText() + { + text=documentElement->GetDocument()->GetText(true); + return text; + } + + void GuiDocumentLabel::SetText(const WString& value) + { + SelectAll(); + SetSelectionText(value); + } + } + } +} + + +/*********************************************************************** +.\CONTROLS\TEXTEDITORPACKAGE\GUITEXTCOMMONINTERFACE.CPP +***********************************************************************/ +#include + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace elements::text; + using namespace compositions; + +/*********************************************************************** +GuiTextBoxCommonInterface::DefaultCallback +***********************************************************************/ + + GuiTextBoxCommonInterface::DefaultCallback::DefaultCallback(elements::GuiColorizedTextElement* _textElement, compositions::GuiGraphicsComposition* _textComposition) + :textElement(_textElement) + ,textComposition(_textComposition) + { + } + + GuiTextBoxCommonInterface::DefaultCallback::~DefaultCallback() + { + } + + TextPos GuiTextBoxCommonInterface::DefaultCallback::GetLeftWord(TextPos pos) + { + return pos; + } + + TextPos GuiTextBoxCommonInterface::DefaultCallback::GetRightWord(TextPos pos) + { + return pos; + } + + void GuiTextBoxCommonInterface::DefaultCallback::GetWord(TextPos pos, TextPos& begin, TextPos& end) + { + begin=pos; + end=pos; + } + + vint GuiTextBoxCommonInterface::DefaultCallback::GetPageRows() + { + return textComposition->GetBounds().Height()/textElement->GetLines().GetRowHeight(); + } + + bool GuiTextBoxCommonInterface::DefaultCallback::BeforeModify(TextPos start, TextPos end, const WString& originalText, WString& inputText) + { + return true; + } + +/*********************************************************************** +GuiTextBoxCommonInterface +***********************************************************************/ + + void GuiTextBoxCommonInterface::InvokeUndoRedoChanged() + { + UndoRedoChanged.Execute(textControl->GetNotifyEventArguments()); + } + + void GuiTextBoxCommonInterface::InvokeModifiedChanged() + { + ModifiedChanged.Execute(textControl->GetNotifyEventArguments()); + } + + void GuiTextBoxCommonInterface::UpdateCaretPoint() + { + GuiGraphicsHost* host=textComposition->GetRelatedGraphicsHost(); + if(host) + { + Rect caret=textElement->GetLines().GetRectFromTextPos(textElement->GetCaretEnd()); + Point view=textElement->GetViewPosition(); + vint x=caret.x1-view.x; + vint y=caret.y2-view.y; + host->SetCaretPoint(Point(x, y), textComposition); + } + } + + void GuiTextBoxCommonInterface::Move(TextPos pos, bool shift) + { + TextPos oldBegin = textElement->GetCaretBegin(); + TextPos oldEnd = textElement->GetCaretEnd(); + +#if defined VCZH_MSVC + if (0 <= pos.row && pos.row < textElement->GetLines().GetCount()) + { + TextLine& line = textElement->GetLines().GetLine(pos.row); + if (pos.column > 0 && UTF16SPFirst(line.text[pos.column - 1]) && UTF16SPSecond(line.text[pos.column])) + { + if (pos < oldBegin) + { + pos.column--; + } + else if (pos > oldBegin) + { + pos.column++; + } + } + } +#endif + + pos = textElement->GetLines().Normalize(pos); + if (!shift) + { + textElement->SetCaretBegin(pos); + } + textElement->SetCaretEnd(pos); + if (textControl) + { + GuiGraphicsHost* host = textComposition->GetRelatedGraphicsHost(); + if (host) + { + if (host->GetFocusedComposition() == textControl->GetFocusableComposition()) + { + textElement->SetCaretVisible(true); + } + } + } + + Rect bounds = textElement->GetLines().GetRectFromTextPos(pos); + Rect view = Rect(textElement->GetViewPosition(), textComposition->GetBounds().GetSize()); + Point viewPoint = view.LeftTop(); + + if (view.x2 > view.x1 && view.y2 > view.y1) + { + if (bounds.x1 < view.x1) + { + viewPoint.x = bounds.x1; + } + else if (bounds.x2 > view.x2) + { + viewPoint.x = bounds.x2 - view.Width(); + } + if (bounds.y1 < view.y1) + { + viewPoint.y = bounds.y1; + } + else if (bounds.y2 > view.y2) + { + viewPoint.y = bounds.y2 - view.Height(); + } + } + + callback->ScrollToView(viewPoint); + UpdateCaretPoint(); + + TextPos newBegin = textElement->GetCaretBegin(); + TextPos newEnd = textElement->GetCaretEnd(); + if (oldBegin != newBegin || oldEnd != newEnd) + { + ICommonTextEditCallback::TextCaretChangedStruct arguments; + arguments.oldBegin = oldBegin; + arguments.oldEnd = oldEnd; + arguments.newBegin = newBegin; + arguments.newEnd = newEnd; + arguments.editVersion = editVersion; + for (vint i = 0; i < textEditCallbacks.Count(); i++) + { + textEditCallbacks[i]->TextCaretChanged(arguments); + } + SelectionChanged.Execute(textControl->GetNotifyEventArguments()); + } + } + + void GuiTextBoxCommonInterface::Modify(TextPos start, TextPos end, const WString& input, bool asKeyInput) + { + if(start>end) + { + TextPos temp=start; + start=end; + end=temp; + } + TextPos originalStart=start; + TextPos originalEnd=end; + WString originalText=textElement->GetLines().GetText(start, end); + WString inputText=input; + if(callback->BeforeModify(start, end, originalText, inputText)) + { + { + ICommonTextEditCallback::TextEditPreviewStruct arguments; + arguments.originalStart=originalStart; + arguments.originalEnd=originalEnd; + arguments.originalText=originalText; + arguments.inputText=inputText; + arguments.editVersion=editVersion; + arguments.keyInput=asKeyInput; + for(vint i=0;iTextEditPreview(arguments); + } + + inputText=arguments.inputText; + if(originalStart!=arguments.originalStart || originalEnd!=arguments.originalEnd) + { + originalStart=arguments.originalStart; + originalEnd=arguments.originalEnd; + originalText=textElement->GetLines().GetText(originalStart, originalEnd); + start=originalStart; + end=originalEnd; + } + } + + SPIN_LOCK(elementModifyLock) + { + end=textElement->GetLines().Modify(start, end, inputText); + } + callback->AfterModify(originalStart, originalEnd, originalText, start, end, inputText); + + editVersion++; + { + ICommonTextEditCallback::TextEditNotifyStruct arguments; + arguments.originalStart=originalStart; + arguments.originalEnd=originalEnd; + arguments.originalText=originalText; + arguments.inputStart=start; + arguments.inputEnd=end; + arguments.inputText=inputText; + arguments.editVersion=editVersion; + arguments.keyInput=asKeyInput; + for(vint i=0;iTextEditNotify(arguments); + } + } + + Move(end, false); + + for(vint i=0;iTextEditFinished(editVersion); + } + + textControl->TextChanged.Execute(textControl->GetNotifyEventArguments()); + } + } + + bool GuiTextBoxCommonInterface::ProcessKey(vint code, bool shift, bool ctrl) + { + if(IGuiShortcutKeyItem* item=internalShortcutKeyManager->TryGetShortcut(ctrl, shift, false, code)) + { + GuiEventArgs arguments; + item->Executed.Execute(arguments); + return true; + } + + TextPos begin=textElement->GetCaretBegin(); + TextPos end=textElement->GetCaretEnd(); + switch(code) + { + case VKEY_ESCAPE: + if(autoComplete && autoComplete->IsListOpening() && !shift && !ctrl) + { + autoComplete->CloseList(); + } + return true; + case VKEY_RETURN: + if(autoComplete && autoComplete->IsListOpening() && !shift && !ctrl) + { + if(autoComplete->ApplySelectedListItem()) + { + preventEnterDueToAutoComplete=true; + return true; + } + } + break; + case VKEY_UP: + if(autoComplete && autoComplete->IsListOpening() && !shift && !ctrl) + { + autoComplete->SelectPreviousListItem(); + } + else + { + end.row--; + Move(end, shift); + } + return true; + case VKEY_DOWN: + if(autoComplete && autoComplete->IsListOpening() && !shift && !ctrl) + { + autoComplete->SelectNextListItem(); + } + else + { + end.row++; + Move(end, shift); + } + return true; + case VKEY_LEFT: + { + if(ctrl) + { + Move(callback->GetLeftWord(end), shift); + } + else + { + if(end.column==0) + { + if(end.row>0) + { + end.row--; + end=textElement->GetLines().Normalize(end); + end.column=textElement->GetLines().GetLine(end.row).dataLength; + } + } + else + { + end.column--; + } + Move(end, shift); + } + } + return true; + case VKEY_RIGHT: + { + if(ctrl) + { + Move(callback->GetRightWord(end), shift); + } + else + { + if(end.column==textElement->GetLines().GetLine(end.row).dataLength) + { + if(end.rowGetLines().GetCount()-1) + { + end.row++; + end.column=0; + } + } + else + { + end.column++; + } + Move(end, shift); + } + } + return true; + case VKEY_HOME: + { + if(ctrl) + { + Move(TextPos(0, 0), shift); + } + else + { + end.column=0; + Move(end, shift); + } + } + return true; + case VKEY_END: + { + if(ctrl) + { + end.row=textElement->GetLines().GetCount()-1; + } + end.column=textElement->GetLines().GetLine(end.row).dataLength; + Move(end, shift); + } + return true; + case VKEY_PRIOR: + { + end.row-=callback->GetPageRows(); + Move(end, shift); + } + return true; + case VKEY_NEXT: + { + end.row+=callback->GetPageRows(); + Move(end, shift); + } + return true; + case VKEY_BACK: + if(!readonly) + { + if(ctrl && !shift) + { + ProcessKey(VKEY_LEFT, true, true); + ProcessKey(VKEY_BACK, false, false); + } + else if(!ctrl && shift) + { + ProcessKey(VKEY_UP, true, false); + ProcessKey(VKEY_BACK, false, false); + } + else + { + if(begin==end) + { + ProcessKey(VKEY_LEFT, true, false); + } + SetSelectionTextAsKeyInput(L""); + } + return true; + } + break; + case VKEY_DELETE: + if(!readonly) + { + if(ctrl && !shift) + { + ProcessKey(VKEY_RIGHT, true, true); + ProcessKey(VKEY_DELETE, false, false); + } + else if(!ctrl && shift) + { + ProcessKey(VKEY_DOWN, true, false); + ProcessKey(VKEY_DELETE, false, false); + } + else + { + if(begin==end) + { + ProcessKey(VKEY_RIGHT, true, false); + } + SetSelectionTextAsKeyInput(L""); + } + return true; + } + break; + } + return false; + } + + void GuiTextBoxCommonInterface::OnGotFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetFocused(true); + textElement->SetCaretVisible(true); + UpdateCaretPoint(); + } + + void GuiTextBoxCommonInterface::OnLostFocus(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetFocused(false); + textElement->SetCaretVisible(false); + } + + void GuiTextBoxCommonInterface::OnCaretNotify(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetCaretVisible(!textElement->GetCaretVisible()); + } + + void GuiTextBoxCommonInterface::OnLeftButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(textControl->GetVisuallyEnabled() && arguments.compositionSource==arguments.eventSource) + { + dragging=true; + TextPos pos=GetNearestTextPos(Point(arguments.x, arguments.y)); + Move(pos, arguments.shift); + } + } + + void GuiTextBoxCommonInterface::OnLeftButtonUp(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(textControl->GetVisuallyEnabled() && arguments.compositionSource==arguments.eventSource) + { + dragging=false; + } + } + + void GuiTextBoxCommonInterface::OnMouseMove(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(textControl->GetVisuallyEnabled() && arguments.compositionSource==arguments.eventSource) + { + if(dragging) + { + TextPos pos=GetNearestTextPos(Point(arguments.x, arguments.y)); + Move(pos, true); + } + } + } + + void GuiTextBoxCommonInterface::OnKeyDown(compositions::GuiGraphicsComposition* sender, compositions::GuiKeyEventArgs& arguments) + { + if(textControl->GetVisuallyEnabled() && arguments.compositionSource==arguments.eventSource) + { + if(ProcessKey(arguments.code, arguments.shift, arguments.ctrl)) + { + arguments.handled=true; + } + } + } + + void GuiTextBoxCommonInterface::OnCharInput(compositions::GuiGraphicsComposition* sender, compositions::GuiCharEventArgs& arguments) + { + if(preventEnterDueToAutoComplete) + { + preventEnterDueToAutoComplete=false; + if(arguments.code==VKEY_RETURN) + { + return; + } + } + if(textControl->GetVisuallyEnabled() && arguments.compositionSource==arguments.eventSource) + { + if(!readonly && arguments.code!=VKEY_ESCAPE && arguments.code!=VKEY_BACK && !arguments.ctrl) + { + SetSelectionTextAsKeyInput(WString(arguments.code)); + } + } + } + + void GuiTextBoxCommonInterface::Install( + elements::GuiColorizedTextElement* _textElement, + compositions::GuiGraphicsComposition* _textComposition, + GuiControl* _textControl, + compositions::GuiGraphicsComposition* eventComposition, + compositions::GuiGraphicsComposition* focusableComposition + ) + { + textElement=_textElement; + textComposition=_textComposition; + textControl=_textControl; + textComposition->SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::IBeam)); + SelectionChanged.SetAssociatedComposition(eventComposition); + UndoRedoChanged.SetAssociatedComposition(eventComposition); + ModifiedChanged.SetAssociatedComposition(eventComposition); + + undoRedoProcessor->UndoRedoChanged.Add(this, &GuiTextBoxCommonInterface::InvokeUndoRedoChanged); + undoRedoProcessor->ModifiedChanged.Add(this, &GuiTextBoxCommonInterface::InvokeModifiedChanged); + + focusableComposition->GetEventReceiver()->gotFocus.AttachMethod(this, &GuiTextBoxCommonInterface::OnGotFocus); + focusableComposition->GetEventReceiver()->lostFocus.AttachMethod(this, &GuiTextBoxCommonInterface::OnLostFocus); + focusableComposition->GetEventReceiver()->caretNotify.AttachMethod(this, &GuiTextBoxCommonInterface::OnCaretNotify); + textComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiTextBoxCommonInterface::OnLeftButtonDown); + textComposition->GetEventReceiver()->leftButtonUp.AttachMethod(this, &GuiTextBoxCommonInterface::OnLeftButtonUp); + textComposition->GetEventReceiver()->mouseMove.AttachMethod(this, &GuiTextBoxCommonInterface::OnMouseMove); + focusableComposition->GetEventReceiver()->keyDown.AttachMethod(this, &GuiTextBoxCommonInterface::OnKeyDown); + focusableComposition->GetEventReceiver()->charInput.AttachMethod(this, &GuiTextBoxCommonInterface::OnCharInput); + + for(vint i=0;iAttach(textElement, elementModifyLock, textComposition ,editVersion); + } + } + + GuiTextBoxCommonInterface::ICallback* GuiTextBoxCommonInterface::GetCallback() + { + return callback; + } + + void GuiTextBoxCommonInterface::SetCallback(ICallback* value) + { + callback=value; + } + + bool GuiTextBoxCommonInterface::AttachTextEditCallback(Ptr value) + { + if(textEditCallbacks.Contains(value.Obj())) + { + return false; + } + else + { + textEditCallbacks.Add(value); + if(textElement) + { + value->Attach(textElement, elementModifyLock, textComposition, editVersion); + } + return true; + } + } + + bool GuiTextBoxCommonInterface::DetachTextEditCallback(Ptr value) + { + if(textEditCallbacks.Remove(value.Obj())) + { + value->Detach(); + return true; + } + else + { + return false; + } + } + + void GuiTextBoxCommonInterface::AddShortcutCommand(vint key, const Func& eventHandler) + { + IGuiShortcutKeyItem* item=internalShortcutKeyManager->CreateShortcut(true, false, false, key); + item->Executed.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) + { + eventHandler(); + }); + } + + elements::GuiColorizedTextElement* GuiTextBoxCommonInterface::GetTextElement() + { + return textElement; + } + + void GuiTextBoxCommonInterface::UnsafeSetText(const WString& value) + { + if(textElement) + { + TextPos end; + if(textElement->GetLines().GetCount()>0) + { + end.row=textElement->GetLines().GetCount()-1; + end.column=textElement->GetLines().GetLine(end.row).dataLength; + } + Modify(TextPos(), end, value, false); + } + } + + GuiTextBoxCommonInterface::GuiTextBoxCommonInterface() + :textElement(0) + ,textComposition(0) + ,editVersion(0) + ,textControl(0) + ,callback(0) + ,dragging(false) + ,readonly(false) + ,preventEnterDueToAutoComplete(false) + { + undoRedoProcessor=new GuiTextBoxUndoRedoProcessor; + AttachTextEditCallback(undoRedoProcessor); + + internalShortcutKeyManager=new GuiShortcutKeyManager; + AddShortcutCommand('Z', Func(this, &GuiTextBoxCommonInterface::Undo)); + AddShortcutCommand('Y', Func(this, &GuiTextBoxCommonInterface::Redo)); + AddShortcutCommand('A', Func(this, &GuiTextBoxCommonInterface::SelectAll)); + AddShortcutCommand('X', Func(this, &GuiTextBoxCommonInterface::Cut)); + AddShortcutCommand('C', Func(this, &GuiTextBoxCommonInterface::Copy)); + AddShortcutCommand('V', Func(this, &GuiTextBoxCommonInterface::Paste)); + } + + GuiTextBoxCommonInterface::~GuiTextBoxCommonInterface() + { + if(colorizer) + { + DetachTextEditCallback(colorizer); + colorizer=0; + } + if(undoRedoProcessor) + { + DetachTextEditCallback(undoRedoProcessor); + undoRedoProcessor=0; + } + + for(vint i=0;iDetach(); + } + textEditCallbacks.Clear(); + } + + //================ clipboard operations + + bool GuiTextBoxCommonInterface::CanCut() + { + return !readonly && textElement->GetCaretBegin()!=textElement->GetCaretEnd() && textElement->GetPasswordChar()==L'\0'; + } + + bool GuiTextBoxCommonInterface::CanCopy() + { + return textElement->GetCaretBegin()!=textElement->GetCaretEnd() && textElement->GetPasswordChar()==L'\0'; + } + + bool GuiTextBoxCommonInterface::CanPaste() + { + if (!readonly && textElement->GetPasswordChar() == L'\0') + { + auto reader = GetCurrentController()->ClipboardService()->ReadClipboard(); + return reader->ContainsText(); + } + return false; + } + + bool GuiTextBoxCommonInterface::Cut() + { + if (!CanCut()) return false; + auto writer = GetCurrentController()->ClipboardService()->WriteClipboard(); + writer->SetText(GetSelectionText()); + writer->Submit(); + SetSelectionText(L""); + return true; + } + + bool GuiTextBoxCommonInterface::Copy() + { + if (!CanCopy()) return false; + auto writer = GetCurrentController()->ClipboardService()->WriteClipboard(); + writer->SetText(GetSelectionText()); + writer->Submit(); + return true; + } + + bool GuiTextBoxCommonInterface::Paste() + { + if (!CanPaste()) return false; + auto reader = GetCurrentController()->ClipboardService()->ReadClipboard(); + SetSelectionText(reader->GetText()); + return true; + } + + //================ editing control + + bool GuiTextBoxCommonInterface::GetReadonly() + { + return readonly; + } + + void GuiTextBoxCommonInterface::SetReadonly(bool value) + { + readonly=value; + } + + //================ text operations + + void GuiTextBoxCommonInterface::Select(TextPos begin, TextPos end) + { + Move(begin, false); + Move(end, true); + } + + void GuiTextBoxCommonInterface::SelectAll() + { + vint row=textElement->GetLines().GetCount()-1; + Move(TextPos(0, 0), false); + Move(TextPos(row, textElement->GetLines().GetLine(row).dataLength), true); + } + + WString GuiTextBoxCommonInterface::GetSelectionText() + { + TextPos selectionBegin=textElement->GetCaretBegin()GetCaretEnd()?textElement->GetCaretBegin():textElement->GetCaretEnd(); + TextPos selectionEnd=textElement->GetCaretBegin()>textElement->GetCaretEnd()?textElement->GetCaretBegin():textElement->GetCaretEnd(); + return textElement->GetLines().GetText(selectionBegin, selectionEnd); + } + + void GuiTextBoxCommonInterface::SetSelectionText(const WString& value) + { + Modify(textElement->GetCaretBegin(), textElement->GetCaretEnd(), value, false); + } + + void GuiTextBoxCommonInterface::SetSelectionTextAsKeyInput(const WString& value) + { + Modify(textElement->GetCaretBegin(), textElement->GetCaretEnd(), value, true); + } + + WString GuiTextBoxCommonInterface::GetRowText(vint row) + { + TextPos start=textElement->GetLines().Normalize(TextPos(row, 0)); + TextPos end=TextPos(start.row, textElement->GetLines().GetLine(start.row).dataLength); + return GetFragmentText(start, end); + } + + vint GuiTextBoxCommonInterface::GetRowCount() + { + return textElement->GetLines().GetCount(); + } + + WString GuiTextBoxCommonInterface::GetFragmentText(TextPos start, TextPos end) + { + start=textElement->GetLines().Normalize(start); + end=textElement->GetLines().Normalize(end); + return textElement->GetLines().GetText(start, end); + } + + TextPos GuiTextBoxCommonInterface::GetCaretBegin() + { + return textElement->GetCaretBegin(); + } + + TextPos GuiTextBoxCommonInterface::GetCaretEnd() + { + return textElement->GetCaretEnd(); + } + + TextPos GuiTextBoxCommonInterface::GetCaretSmall() + { + TextPos c1=GetCaretBegin(); + TextPos c2=GetCaretEnd(); + return c1c2?c1:c2; + } + + //================ position query + + vint GuiTextBoxCommonInterface::GetRowWidth(vint row) + { + return textElement->GetLines().GetRowWidth(row); + } + + vint GuiTextBoxCommonInterface::GetRowHeight() + { + return textElement->GetLines().GetRowHeight(); + } + + vint GuiTextBoxCommonInterface::GetMaxWidth() + { + return textElement->GetLines().GetMaxWidth(); + } + + vint GuiTextBoxCommonInterface::GetMaxHeight() + { + return textElement->GetLines().GetMaxHeight(); + } + + TextPos GuiTextBoxCommonInterface::GetTextPosFromPoint(Point point) + { + Point view=textElement->GetViewPosition(); + return textElement->GetLines().GetTextPosFromPoint(Point(point.x+view.x, point.y+view.y)); + } + + Point GuiTextBoxCommonInterface::GetPointFromTextPos(TextPos pos) + { + Point view=textElement->GetViewPosition(); + Point result=textElement->GetLines().GetPointFromTextPos(pos); + return Point(result.x-view.x, result.y-view.y); + } + + Rect GuiTextBoxCommonInterface::GetRectFromTextPos(TextPos pos) + { + Point view=textElement->GetViewPosition(); + Rect result=textElement->GetLines().GetRectFromTextPos(pos); + return Rect(Point(result.x1-view.x, result.y1-view.y), result.GetSize()); + } + + TextPos GuiTextBoxCommonInterface::GetNearestTextPos(Point point) + { + Point viewPosition=textElement->GetViewPosition(); + Point mousePosition=Point(point.x+viewPosition.x, point.y+viewPosition.y); + TextPos pos=textElement->GetLines().GetTextPosFromPoint(mousePosition); + if(pos.columnGetLines().GetLine(pos.row).dataLength) + { + Rect rect=textElement->GetLines().GetRectFromTextPos(pos); + if(abs((int)(rect.x1-mousePosition.x))>=abs((int)(rect.x2-1-mousePosition.x))) + { + pos.column++; + } + } + return pos; + } + + //================ colorizing + + Ptr GuiTextBoxCommonInterface::GetColorizer() + { + return colorizer; + } + + void GuiTextBoxCommonInterface::SetColorizer(Ptr value) + { + if (!filledDefaultColors) + { + filledDefaultColors = true; + CopyFrom(defaultColors, GetTextElement()->GetColors()); + } + + if(colorizer) + { + DetachTextEditCallback(colorizer); + } + colorizer=value; + if(colorizer) + { + AttachTextEditCallback(colorizer); + GetTextElement()->SetColors(colorizer->GetColors()); + } + else + { + GetTextElement()->SetColors(defaultColors); + GetTextElement()->ResetTextColorIndex(0); + } + } + + //================ auto complete + + Ptr GuiTextBoxCommonInterface::GetAutoComplete() + { + return autoComplete; + } + + void GuiTextBoxCommonInterface::SetAutoComplete(Ptr value) + { + if(autoComplete) + { + DetachTextEditCallback(autoComplete); + } + autoComplete=value; + if(autoComplete) + { + AttachTextEditCallback(autoComplete); + } + } + + //================ undo redo control + + vuint GuiTextBoxCommonInterface::GetEditVersion() + { + return editVersion; + } + + bool GuiTextBoxCommonInterface::CanUndo() + { + return !readonly && undoRedoProcessor->CanUndo(); + } + + bool GuiTextBoxCommonInterface::CanRedo() + { + return !readonly && undoRedoProcessor->CanRedo(); + } + + void GuiTextBoxCommonInterface::ClearUndoRedo() + { + undoRedoProcessor->ClearUndoRedo(); + } + + bool GuiTextBoxCommonInterface::GetModified() + { + return undoRedoProcessor->GetModified(); + } + + void GuiTextBoxCommonInterface::NotifyModificationSaved() + { + undoRedoProcessor->NotifyModificationSaved(); + } + + bool GuiTextBoxCommonInterface::Undo() + { + if(CanUndo()) + { + return undoRedoProcessor->Undo(); + } + else + { + return false; + } + } + + bool GuiTextBoxCommonInterface::Redo() + { + if(CanRedo()) + { + return undoRedoProcessor->Redo(); + } + else + { + return false; + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEXTEDITORPACKAGE\GUITEXTCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace elements::text; + using namespace compositions; + using namespace collections; + +/*********************************************************************** +GuiMultilineTextBox::DefaultTextElementOperatorCallback +***********************************************************************/ + + GuiMultilineTextBox::TextElementOperatorCallback::TextElementOperatorCallback(GuiMultilineTextBox* _textControl) + :GuiTextBoxCommonInterface::DefaultCallback( + _textControl->textElement, + _textControl->textComposition + ) + ,textControl(_textControl) + { + } + + void GuiMultilineTextBox::TextElementOperatorCallback::AfterModify(TextPos originalStart, TextPos originalEnd, const WString& originalText, TextPos inputStart, TextPos inputEnd, const WString& inputText) + { + textControl->CalculateView(); + } + + void GuiMultilineTextBox::TextElementOperatorCallback::ScrollToView(Point point) + { + point.x+=TextMargin; + point.y+=TextMargin; + Point oldPoint = textControl->GetViewPosition(); + vint marginX=0; + vint marginY=0; + if(oldPoint.xpoint.x) + { + marginX=-TextMargin; + } + if(oldPoint.ypoint.y) + { + marginY=-TextMargin; + } + textControl->SetViewPosition(Point(point.x + marginX, point.y + marginY)); + } + + vint GuiMultilineTextBox::TextElementOperatorCallback::GetTextMargin() + { + return TextMargin; + } + +/*********************************************************************** +GuiMultilineTextBox::CommandExecutor +***********************************************************************/ + + GuiMultilineTextBox::CommandExecutor::CommandExecutor(GuiMultilineTextBox* _textBox) + :textBox(_textBox) + { + } + + GuiMultilineTextBox::CommandExecutor::~CommandExecutor() + { + } + + void GuiMultilineTextBox::CommandExecutor::UnsafeSetText(const WString& value) + { + textBox->UnsafeSetText(value); + } + +/*********************************************************************** +GuiMultilineTextBox +***********************************************************************/ + + void GuiMultilineTextBox::BeforeControlTemplateUninstalled_() + { + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + ct->SetCommands(nullptr); + } + + void GuiMultilineTextBox::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + Array colors(1); + colors[0] = ct->GetTextColor(); + textElement->SetColors(colors); + textElement->SetCaretColor(ct->GetCaretColor()); + ct->SetCommands(commandExecutor.Obj()); + } + + void GuiMultilineTextBox::CalculateViewAndSetScroll() + { + auto ct = GetControlTemplateObject(true); + CalculateView(); + vint smallMove = textElement->GetLines().GetRowHeight(); + vint bigMove = smallMove * 5; + + if (auto scroll = ct->GetHorizontalScroll()) + { + scroll->SetSmallMove(smallMove); + scroll->SetBigMove(bigMove); + } + + if (auto scroll = ct->GetVerticalScroll()) + { + scroll->SetSmallMove(smallMove); + scroll->SetBigMove(bigMove); + } + } + + void GuiMultilineTextBox::OnVisuallyEnabledChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetVisuallyEnabled(GetVisuallyEnabled()); + } + + Size GuiMultilineTextBox::QueryFullSize() + { + text::TextLines& lines = textElement->GetLines(); + return Size(lines.GetMaxWidth() + TextMargin * 2, lines.GetMaxHeight() + TextMargin * 2); + } + + void GuiMultilineTextBox::UpdateView(Rect viewBounds) + { + textElement->SetViewPosition(viewBounds.LeftTop() - Size(TextMargin, TextMargin)); + } + + void GuiMultilineTextBox::OnRenderTargetChanged(elements::IGuiGraphicsRenderTarget* renderTarget) + { + CalculateViewAndSetScroll(); + GuiScrollView::OnRenderTargetChanged(renderTarget); + } + + void GuiMultilineTextBox::OnBoundsMouseButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(GetVisuallyEnabled()) + { + boundsComposition->GetRelatedGraphicsHost()->SetFocus(boundsComposition); + } + } + + GuiMultilineTextBox::GuiMultilineTextBox(theme::ThemeName themeName) + :GuiScrollView(themeName) + { + textElement = GuiColorizedTextElement::Create(); + textElement->SetFont(GetFont()); + + textComposition = new GuiBoundsComposition; + textComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + textComposition->SetOwnedElement(textElement); + containerComposition->AddChild(textComposition); + + callback = new TextElementOperatorCallback(this); + commandExecutor = new CommandExecutor(this); + + SetFocusableComposition(boundsComposition); + Install(textElement, textComposition, this, boundsComposition, focusableComposition); + SetCallback(callback.Obj()); + + VisuallyEnabledChanged.AttachMethod(this, &GuiMultilineTextBox::OnVisuallyEnabledChanged); + boundsComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiMultilineTextBox::OnBoundsMouseButtonDown); + boundsComposition->GetEventReceiver()->middleButtonDown.AttachMethod(this, &GuiMultilineTextBox::OnBoundsMouseButtonDown); + boundsComposition->GetEventReceiver()->rightButtonDown.AttachMethod(this, &GuiMultilineTextBox::OnBoundsMouseButtonDown); + } + + GuiMultilineTextBox::~GuiMultilineTextBox() + { + } + + const WString& GuiMultilineTextBox::GetText() + { + text = textElement->GetLines().GetText(); + return text; + } + + void GuiMultilineTextBox::SetText(const WString& value) + { + UnsafeSetText(value); + textElement->SetCaretBegin(TextPos(0, 0)); + textElement->SetCaretEnd(TextPos(0, 0)); + CalculateView(); + } + + void GuiMultilineTextBox::SetFont(const FontProperties& value) + { + GuiControl::SetFont(value); + textElement->SetFont(value); + CalculateViewAndSetScroll(); + } + +/*********************************************************************** +GuiSinglelineTextBox::DefaultTextElementOperatorCallback +***********************************************************************/ + + GuiSinglelineTextBox::TextElementOperatorCallback::TextElementOperatorCallback(GuiSinglelineTextBox* _textControl) + :GuiTextBoxCommonInterface::DefaultCallback( + _textControl->textElement, + _textControl->textComposition + ) + { + } + + bool GuiSinglelineTextBox::TextElementOperatorCallback::BeforeModify(TextPos start, TextPos end, const WString& originalText, WString& inputText) + { + vint length=inputText.Length(); + const wchar_t* input=inputText.Buffer(); + for(vint i=0;iGetViewPosition().x; + vint marginX=0; + if(oldXnewX) + { + marginX=-TextMargin; + } + + newX+=marginX; + vint minX=-TextMargin; + vint maxX=textElement->GetLines().GetMaxWidth()+TextMargin-textComposition->GetBounds().Width(); + if(newX>=maxX) + { + newX=maxX-1; + } + if(newXSetViewPosition(Point(newX, -TextMargin)); + } + + vint GuiSinglelineTextBox::TextElementOperatorCallback::GetTextMargin() + { + return TextMargin; + } + +/*********************************************************************** +GuiSinglelineTextBox +***********************************************************************/ + + void GuiSinglelineTextBox::BeforeControlTemplateUninstalled_() + { + } + + void GuiSinglelineTextBox::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + Array colors(1); + colors[0] = ct->GetTextColor(); + textElement->SetColors(colors); + textElement->SetCaretColor(ct->GetCaretColor()); + } + + void GuiSinglelineTextBox::RearrangeTextElement() + { + textCompositionTable->SetRowOption( + 1, + GuiCellOption::AbsoluteOption( + textElement->GetLines().GetRowHeight() + 2 * TextMargin) + ); + } + + void GuiSinglelineTextBox::OnRenderTargetChanged(elements::IGuiGraphicsRenderTarget* renderTarget) + { + GuiControl::OnRenderTargetChanged(renderTarget); + RearrangeTextElement(); + } + + void GuiSinglelineTextBox::OnVisuallyEnabledChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + textElement->SetVisuallyEnabled(GetVisuallyEnabled()); + } + + void GuiSinglelineTextBox::OnBoundsMouseButtonDown(compositions::GuiGraphicsComposition* sender, compositions::GuiMouseEventArgs& arguments) + { + if(GetVisuallyEnabled()) + { + boundsComposition->GetRelatedGraphicsHost()->SetFocus(boundsComposition); + } + } + + GuiSinglelineTextBox::GuiSinglelineTextBox(theme::ThemeName themeName) + :GuiControl(themeName) + { + textElement = GuiColorizedTextElement::Create(); + textElement->SetFont(GetFont()); + textElement->SetViewPosition(Point(-GuiSinglelineTextBox::TextMargin, -GuiSinglelineTextBox::TextMargin)); + + textCompositionTable = new GuiTableComposition; + textCompositionTable->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + textCompositionTable->SetAlignmentToParent(Margin(0, 0, 0, 0)); + textCompositionTable->SetRowsAndColumns(3, 1); + textCompositionTable->SetRowOption(0, GuiCellOption::PercentageOption(0.5)); + textCompositionTable->SetRowOption(1, GuiCellOption::AbsoluteOption(0)); + textCompositionTable->SetRowOption(2, GuiCellOption::PercentageOption(0.5)); + textCompositionTable->SetColumnOption(0, GuiCellOption::PercentageOption(1.0)); + containerComposition->AddChild(textCompositionTable); + + textComposition = new GuiCellComposition; + textComposition->SetOwnedElement(textElement); + textCompositionTable->AddChild(textComposition); + textComposition->SetSite(1, 0, 1, 1); + + callback = new TextElementOperatorCallback(this); + SetFocusableComposition(boundsComposition); + Install(textElement, textComposition, this, boundsComposition, focusableComposition); + SetCallback(callback.Obj()); + + VisuallyEnabledChanged.AttachMethod(this, &GuiSinglelineTextBox::OnVisuallyEnabledChanged); + boundsComposition->GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiSinglelineTextBox::OnBoundsMouseButtonDown); + boundsComposition->GetEventReceiver()->middleButtonDown.AttachMethod(this, &GuiSinglelineTextBox::OnBoundsMouseButtonDown); + boundsComposition->GetEventReceiver()->rightButtonDown.AttachMethod(this, &GuiSinglelineTextBox::OnBoundsMouseButtonDown); + } + + GuiSinglelineTextBox::~GuiSinglelineTextBox() + { + } + + const WString& GuiSinglelineTextBox::GetText() + { + text = textElement->GetLines().GetText(); + return text; + } + + void GuiSinglelineTextBox::SetText(const WString& value) + { + UnsafeSetText(value); + textElement->SetCaretBegin(TextPos(0, 0)); + textElement->SetCaretEnd(TextPos(0, 0)); + } + + void GuiSinglelineTextBox::SetFont(const FontProperties& value) + { + GuiControl::SetFont(value); + textElement->SetFont(value); + RearrangeTextElement(); + } + + wchar_t GuiSinglelineTextBox::GetPasswordChar() + { + return textElement->GetPasswordChar(); + } + + void GuiSinglelineTextBox::SetPasswordChar(wchar_t value) + { + textElement->SetPasswordChar(value); + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEXTEDITORPACKAGE\EDITORCALLBACK\GUITEXTAUTOCOMPLETE.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + +/*********************************************************************** +GuiTextBoxAutoCompleteBase::TextListControlProvider +***********************************************************************/ + + GuiTextBoxAutoCompleteBase::TextListControlProvider::TextListControlProvider(TemplateProperty controlTemplate) + { + autoCompleteList = new GuiTextList(theme::ThemeName::TextList); + if (controlTemplate) + { + autoCompleteList->SetControlTemplate(controlTemplate); + } + autoCompleteList->SetHorizontalAlwaysVisible(false); + autoCompleteList->SetVerticalAlwaysVisible(false); + } + + GuiTextBoxAutoCompleteBase::TextListControlProvider::~TextListControlProvider() + { + } + + GuiControl* GuiTextBoxAutoCompleteBase::TextListControlProvider::GetAutoCompleteControl() + { + return autoCompleteList; + } + + GuiSelectableListControl* GuiTextBoxAutoCompleteBase::TextListControlProvider::GetListControl() + { + return autoCompleteList; + } + + void GuiTextBoxAutoCompleteBase::TextListControlProvider::SetSortedContent(const collections::List& items) + { + autoCompleteList->GetItems().Clear(); + FOREACH(AutoCompleteItem, item, items) + { + autoCompleteList->GetItems().Add(new list::TextItem(item.text)); + } + } + + vint GuiTextBoxAutoCompleteBase::TextListControlProvider::GetItemCount() + { + return autoCompleteList->GetItems().Count(); + } + + WString GuiTextBoxAutoCompleteBase::TextListControlProvider::GetItemText(vint index) + { + return autoCompleteList->GetItems()[index]->GetText(); + } + +/*********************************************************************** +GuiTextBoxAutoCompleteBase +***********************************************************************/ + + bool GuiTextBoxAutoCompleteBase::IsPrefix(const WString& prefix, const WString& candidate) + { + if(candidate.Length()>=prefix.Length()) + { + if(INVLOC.Compare(prefix, candidate.Sub(0, prefix.Length()), Locale::IgnoreCase)==0) + { + return true; + } + } + return false; + } + + GuiTextBoxAutoCompleteBase::GuiTextBoxAutoCompleteBase(Ptr _autoCompleteControlProvider) + :element(0) + , elementModifyLock(0) + , ownerComposition(0) + , autoCompleteControlProvider(_autoCompleteControlProvider) + { + if (!autoCompleteControlProvider) + { + autoCompleteControlProvider = new TextListControlProvider; + } + autoCompleteControlProvider->GetAutoCompleteControl()->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + + autoCompletePopup = new GuiPopup(theme::ThemeName::Menu); + autoCompletePopup->AddChild(autoCompleteControlProvider->GetAutoCompleteControl()); + } + + GuiTextBoxAutoCompleteBase::~GuiTextBoxAutoCompleteBase() + { + delete autoCompletePopup; + } + + void GuiTextBoxAutoCompleteBase::Attach(elements::GuiColorizedTextElement* _element, SpinLock& _elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) + { + if(_element) + { + SPIN_LOCK(_elementModifyLock) + { + element=_element; + elementModifyLock=&_elementModifyLock; + ownerComposition=_ownerComposition; + } + } + } + + void GuiTextBoxAutoCompleteBase::Detach() + { + if(element && elementModifyLock) + { + SPIN_LOCK(*elementModifyLock) + { + element=0; + elementModifyLock=0; + } + } + } + + void GuiTextBoxAutoCompleteBase::TextEditPreview(TextEditPreviewStruct& arguments) + { + } + + void GuiTextBoxAutoCompleteBase::TextEditNotify(const TextEditNotifyStruct& arguments) + { + if(element && elementModifyLock) + { + if(IsListOpening()) + { + TextPos begin=GetListStartPosition(); + TextPos end=arguments.inputEnd; + WString editingText=element->GetLines().GetText(begin, end); + HighlightList(editingText); + } + } + } + + void GuiTextBoxAutoCompleteBase::TextCaretChanged(const TextCaretChangedStruct& arguments) + { + } + + void GuiTextBoxAutoCompleteBase::TextEditFinished(vuint editVersion) + { + } + + bool GuiTextBoxAutoCompleteBase::IsListOpening() + { + return autoCompletePopup->GetOpening(); + } + + void GuiTextBoxAutoCompleteBase::OpenList(TextPos startPosition) + { + if(element && elementModifyLock) + { + autoCompleteStartPosition=startPosition; + Rect bounds=element->GetLines().GetRectFromTextPos(startPosition); + Point viewPosition=element->GetViewPosition(); + GuiControl* ownerControl=ownerComposition->GetRelatedControl(); + Rect compositionBounds=ownerComposition->GetGlobalBounds(); + Rect controlBounds=ownerControl->GetBoundsComposition()->GetGlobalBounds(); + vint px=compositionBounds.x1-controlBounds.x1-viewPosition.x; + vint py=compositionBounds.y1-controlBounds.y1-viewPosition.y; + bounds.x1+=px; + bounds.x2+=px; + bounds.y1+=py+5; + bounds.y2+=py+5; + autoCompletePopup->ShowPopup(ownerControl, bounds, true); + } + } + + void GuiTextBoxAutoCompleteBase::CloseList() + { + autoCompletePopup->Close(); + } + + void GuiTextBoxAutoCompleteBase::SetListContent(const collections::List& items) + { + if(items.Count()==0) + { + CloseList(); + } + + List sortedItems; + CopyFrom( + sortedItems, + From(items) + .OrderBy([](const AutoCompleteItem& a, const AutoCompleteItem& b) + { + return INVLOC.Compare(a.text, b.text, Locale::IgnoreCase); + }) + ); + + autoCompleteControlProvider->SetSortedContent(sortedItems); + autoCompleteControlProvider->GetAutoCompleteControl()->GetBoundsComposition()->SetPreferredMinSize(Size(200, 200)); + } + + TextPos GuiTextBoxAutoCompleteBase::GetListStartPosition() + { + return autoCompleteStartPosition; + } + + bool GuiTextBoxAutoCompleteBase::SelectPreviousListItem() + { + if(!IsListOpening()) return false; + if(autoCompleteControlProvider->GetListControl()->GetSelectedItems().Count()==0) + { + autoCompleteControlProvider->GetListControl()->SetSelected(0, true); + } + else + { + vint index=autoCompleteControlProvider->GetListControl()->GetSelectedItems()[0]; + if (index > 0) index--; + autoCompleteControlProvider->GetListControl()->SetSelected(index, true); + autoCompleteControlProvider->GetListControl()->EnsureItemVisible(index); + } + return true; + } + + bool GuiTextBoxAutoCompleteBase::SelectNextListItem() + { + if(!IsListOpening()) return false; + if (autoCompleteControlProvider->GetListControl()->GetSelectedItems().Count() == 0) + { + autoCompleteControlProvider->GetListControl()->SetSelected(0, true); + } + else + { + vint index = autoCompleteControlProvider->GetListControl()->GetSelectedItems()[0]; + if (index < autoCompleteControlProvider->GetItemCount() - 1) index++; + autoCompleteControlProvider->GetListControl()->SetSelected(index, true); + autoCompleteControlProvider->GetListControl()->EnsureItemVisible(index); + } + return true; + } + + bool GuiTextBoxAutoCompleteBase::ApplySelectedListItem() + { + if(!IsListOpening()) return false; + if(!ownerComposition) return false; + const auto& selectedItems = autoCompleteControlProvider->GetListControl()->GetSelectedItems(); + if (selectedItems.Count() == 0) return false; + GuiTextBoxCommonInterface* ci=dynamic_cast(ownerComposition->GetRelatedControl()); + if(!ci) return false; + + vint index = selectedItems[0]; + WString selectedItem = autoCompleteControlProvider->GetItemText(index); + TextPos begin = autoCompleteStartPosition; + TextPos end = ci->GetCaretEnd(); + ci->Select(begin, end); + ci->SetSelectionText(selectedItem); + CloseList(); + return true; + } + + WString GuiTextBoxAutoCompleteBase::GetSelectedListItem() + { + if(!IsListOpening()) return L""; + const auto& selectedItems = autoCompleteControlProvider->GetListControl()->GetSelectedItems(); + if (selectedItems.Count() == 0) return L""; + vint index = selectedItems[0]; + return autoCompleteControlProvider->GetItemText(index); + } + + void GuiTextBoxAutoCompleteBase::HighlightList(const WString& editingText) + { + if(IsListOpening()) + { + vint first=0; + vint last = autoCompleteControlProvider->GetItemCount() - 1; + vint selected=-1; + + while (first <= last) + { + vint middle = (first + last) / 2; + WString text = autoCompleteControlProvider->GetItemText(middle); + if (IsPrefix(editingText, text)) + { + selected = middle; + break; + } + + vint result = INVLOC.Compare(editingText, text, Locale::IgnoreCase); + if (result <= 0) + { + last = middle - 1; + } + else + { + first = middle + 1; + } + } + + while(selected>0) + { + WString text = autoCompleteControlProvider->GetItemText(selected - 1); + if (IsPrefix(editingText, text)) + { + selected--; + } + else + { + break; + } + } + + if(selected!=-1) + { + autoCompleteControlProvider->GetListControl()->SetSelected(selected, true); + autoCompleteControlProvider->GetListControl()->EnsureItemVisible(selected); + } + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEXTEDITORPACKAGE\EDITORCALLBACK\GUITEXTCOLORIZER.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace elements::text; + +/*********************************************************************** +GuiTextBoxColorizerBase +***********************************************************************/ + + void GuiTextBoxColorizerBase::ColorizerThreadProc(void* argument) + { + GuiTextBoxColorizerBase* colorizer=(GuiTextBoxColorizerBase*)argument; + while(!colorizer->isFinalizing) + { + vint lineIndex=-1; + wchar_t* text=0; + vuint32_t* colors=0; + vint length=0; + vint lexerState=-1; + vint contextState=-1; + + SPIN_LOCK(*colorizer->elementModifyLock) + { + if(colorizer->colorizedLineCount>=colorizer->element->GetLines().GetCount()) + { + colorizer->isColorizerRunning=false; + goto CANCEL_COLORIZING; + } + + lineIndex=colorizer->colorizedLineCount++; + TextLine& line=colorizer->element->GetLines().GetLine(lineIndex); + length=line.dataLength; + text=new wchar_t[length+2]; + colors=new vuint32_t[length+2]; + memcpy(text, line.text, sizeof(wchar_t)*length); + text[length]=L'\r'; + text[length+1]=L'\n'; + lexerState=lineIndex==0?colorizer->GetLexerStartState():colorizer->element->GetLines().GetLine(lineIndex-1).lexerFinalState; + contextState=lineIndex==0?colorizer->GetContextStartState():colorizer->element->GetLines().GetLine(lineIndex-1).contextFinalState; + } + + colorizer->ColorizeLineWithCRLF(lineIndex, text, colors, length+2, lexerState, contextState); + + SPIN_LOCK(*colorizer->elementModifyLock) + { + if(lineIndexcolorizedLineCount && lineIndexelement->GetLines().GetCount()) + { + TextLine& line=colorizer->element->GetLines().GetLine(lineIndex); + line.lexerFinalState=lexerState; + line.contextFinalState=contextState; + for(vint i=0;icolorizerRunningEvent.Leave(); + } + + void GuiTextBoxColorizerBase::StartColorizer() + { + if(!isColorizerRunning) + { + isColorizerRunning=true; + colorizerRunningEvent.Enter(); + ThreadPoolLite::Queue(&GuiTextBoxColorizerBase::ColorizerThreadProc, this); + } + } + + void GuiTextBoxColorizerBase::StopColorizer(bool forever) + { + isFinalizing=true; + colorizerRunningEvent.Enter(); + colorizerRunningEvent.Leave(); + colorizedLineCount=0; + if(!forever) + { + isFinalizing=false; + } + } + + void GuiTextBoxColorizerBase::StopColorizerForever() + { + StopColorizer(true); + } + + GuiTextBoxColorizerBase::GuiTextBoxColorizerBase() + :element(0) + ,elementModifyLock(0) + ,colorizedLineCount(0) + ,isColorizerRunning(false) + ,isFinalizing(false) + { + } + + GuiTextBoxColorizerBase::~GuiTextBoxColorizerBase() + { + StopColorizerForever(); + } + + void GuiTextBoxColorizerBase::Attach(elements::GuiColorizedTextElement* _element, SpinLock& _elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) + { + if(_element) + { + SPIN_LOCK(_elementModifyLock) + { + element=_element; + elementModifyLock=&_elementModifyLock; + StartColorizer(); + } + } + } + + void GuiTextBoxColorizerBase::Detach() + { + if(element && elementModifyLock) + { + StopColorizer(false); + SPIN_LOCK(*elementModifyLock) + { + element=0; + elementModifyLock=0; + } + } + } + + void GuiTextBoxColorizerBase::TextEditPreview(TextEditPreviewStruct& arguments) + { + } + + void GuiTextBoxColorizerBase::TextEditNotify(const TextEditNotifyStruct& arguments) + { + if(element && elementModifyLock) + { + SPIN_LOCK(*elementModifyLock) + { + vint line + =arguments.originalStart.rowline) + { + colorizedLineCount=line; + } + StartColorizer(); + } + } + } + + void GuiTextBoxColorizerBase::TextCaretChanged(const TextCaretChangedStruct& arguments) + { + } + + void GuiTextBoxColorizerBase::TextEditFinished(vuint editVersion) + { + } + + void GuiTextBoxColorizerBase::RestartColorizer() + { + if(element && elementModifyLock) + { + SPIN_LOCK(*elementModifyLock) + { + colorizedLineCount=0; + StartColorizer(); + } + } + } + +/*********************************************************************** +GuiTextBoxRegexColorizer +***********************************************************************/ + + struct GuiTextBoxRegexColorizerProcData + { + GuiTextBoxRegexColorizer* colorizer; + vint lineIndex; + const wchar_t* text; + vuint32_t* colors; + vint contextState; + }; + + void GuiTextBoxRegexColorizer::ColorizerProc(void* argument, vint start, vint length, vint token) + { + GuiTextBoxRegexColorizerProcData& data=*(GuiTextBoxRegexColorizerProcData*)argument; + data.colorizer->ColorizeTokenContextSensitive(data.lineIndex, data.text, start, length, token, data.contextState); + for(vint i=0;i& GuiTextBoxRegexColorizer::GetTokenRegexes() + { + return tokenRegexes; + } + + collections::List& GuiTextBoxRegexColorizer::GetTokenColors() + { + return tokenColors; + } + + collections::List& GuiTextBoxRegexColorizer::GetExtraTokenColors() + { + return extraTokenColors; + } + + vint GuiTextBoxRegexColorizer::GetExtraTokenIndexStart() + { + if(lexer) + { + return tokenColors.Count(); + } + else + { + return -1; + } + } + + bool GuiTextBoxRegexColorizer::SetDefaultColor(elements::text::ColorEntry value) + { + if(lexer) + { + return false; + } + else + { + defaultColor=value; + return true; + } + } + + vint GuiTextBoxRegexColorizer::AddToken(const WString& regex, elements::text::ColorEntry color) + { + if(lexer) + { + return -1; + } + else + { + tokenRegexes.Add(regex); + tokenColors.Add(color); + return tokenColors.Count()-1; + } + } + + vint GuiTextBoxRegexColorizer::AddExtraToken(elements::text::ColorEntry color) + { + if(lexer) + { + return -1; + } + else + { + extraTokenColors.Add(color); + return extraTokenColors.Count()-1; + } + } + + void GuiTextBoxRegexColorizer::ClearTokens() + { + tokenRegexes.Clear(); + tokenColors.Clear(); + extraTokenColors.Clear(); + lexer=0; + } + + void GuiTextBoxRegexColorizer::Setup() + { + if(lexer || tokenRegexes.Count()==0) + { + colors.Resize(1); + colors[0]=defaultColor; + } + else + { + lexer=new regex::RegexLexer(tokenRegexes); + colors.Resize(1+tokenRegexes.Count()+extraTokenColors.Count()); + colors[0]=defaultColor; + for(vint i=0;iColorize()); + } + } + + void GuiTextBoxRegexColorizer::ColorizeTokenContextSensitive(vint lineIndex, const wchar_t* text, vint start, vint length, vint& token, vint& contextState) + { + } + + vint GuiTextBoxRegexColorizer::GetLexerStartState() + { + return lexer?colorizer->GetStartState():-1; + } + + vint GuiTextBoxRegexColorizer::GetContextStartState() + { + return 0; + } + + void GuiTextBoxRegexColorizer::ColorizeLineWithCRLF(vint lineIndex, const wchar_t* text, vuint32_t* colors, vint length, vint& lexerState, vint& contextState) + { + memset(colors, 0, sizeof(*colors)*length); + if(lexer) + { + GuiTextBoxRegexColorizerProcData data; + data.colorizer=this; + data.lineIndex=lineIndex; + data.text=text; + data.colors=colors; + data.contextState=contextState; + + colorizer->Reset(lexerState); + colorizer->Colorize(text, length, &GuiTextBoxRegexColorizer::ColorizerProc, &data); + + lexerState=colorizer->GetCurrentState(); + contextState=data.contextState; + } + else + { + lexerState=-1; + contextState=-1; + } + } + + const GuiTextBoxRegexColorizer::ColorArray& GuiTextBoxRegexColorizer::GetColors() + { + return colors; + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEXTEDITORPACKAGE\EDITORCALLBACK\GUITEXTGENERALOPERATIONS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + } + } +} + +/*********************************************************************** +.\CONTROLS\TEXTEDITORPACKAGE\EDITORCALLBACK\GUITEXTUNDOREDO.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace elements::text; + using namespace compositions; + +/*********************************************************************** +GuiGeneralUndoRedoProcessor +***********************************************************************/ + + GuiGeneralUndoRedoProcessor::GuiGeneralUndoRedoProcessor() + :firstFutureStep(0) + ,savedStep(0) + ,performingUndoRedo(false) + { + } + + GuiGeneralUndoRedoProcessor::~GuiGeneralUndoRedoProcessor() + { + } + + void GuiGeneralUndoRedoProcessor::PushStep(Ptr step) + { + if(!performingUndoRedo) + { + if(firstFutureStep0) + { + steps.RemoveRange(firstFutureStep, count); + } + + steps.Add(step); + firstFutureStep=steps.Count(); + UndoRedoChanged(); + ModifiedChanged(); + } + } + + bool GuiGeneralUndoRedoProcessor::CanUndo() + { + return firstFutureStep>0; + } + + bool GuiGeneralUndoRedoProcessor::CanRedo() + { + return steps.Count()>firstFutureStep; + } + + void GuiGeneralUndoRedoProcessor::ClearUndoRedo() + { + if(!performingUndoRedo) + { + steps.Clear(); + firstFutureStep=0; + savedStep=0; + } + } + + bool GuiGeneralUndoRedoProcessor::GetModified() + { + return firstFutureStep!=savedStep; + } + + void GuiGeneralUndoRedoProcessor::NotifyModificationSaved() + { + if(!performingUndoRedo) + { + savedStep=firstFutureStep; + ModifiedChanged(); + } + } + + bool GuiGeneralUndoRedoProcessor::Undo() + { + if(!CanUndo()) return false; + performingUndoRedo=true; + firstFutureStep--; + steps[firstFutureStep]->Undo(); + performingUndoRedo=false; + UndoRedoChanged(); + ModifiedChanged(); + return true; + } + + bool GuiGeneralUndoRedoProcessor::Redo() + { + if(!CanRedo()) return false; + performingUndoRedo=true; + firstFutureStep++; + steps[firstFutureStep-1]->Redo(); + performingUndoRedo=false; + UndoRedoChanged(); + ModifiedChanged(); + return true; + } + +/*********************************************************************** +GuiTextBoxUndoRedoProcessor::EditStep +***********************************************************************/ + + void GuiTextBoxUndoRedoProcessor::EditStep::Undo() + { + GuiTextBoxCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); + if(ci) + { + ci->Select(arguments.inputStart, arguments.inputEnd); + ci->SetSelectionText(arguments.originalText); + ci->Select(arguments.originalStart, arguments.originalEnd); + } + } + + void GuiTextBoxUndoRedoProcessor::EditStep::Redo() + { + GuiTextBoxCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); + if(ci) + { + ci->Select(arguments.originalStart, arguments.originalEnd); + ci->SetSelectionText(arguments.inputText); + ci->Select(arguments.inputStart, arguments.inputEnd); + } + } + +/*********************************************************************** +GuiTextBoxUndoRedoProcessor +***********************************************************************/ + + GuiTextBoxUndoRedoProcessor::GuiTextBoxUndoRedoProcessor() + :ownerComposition(0) + { + } + + GuiTextBoxUndoRedoProcessor::~GuiTextBoxUndoRedoProcessor() + { + } + + void GuiTextBoxUndoRedoProcessor::Attach(elements::GuiColorizedTextElement* element, SpinLock& elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) + { + ownerComposition=_ownerComposition; + } + + void GuiTextBoxUndoRedoProcessor::Detach() + { + ClearUndoRedo(); + } + + void GuiTextBoxUndoRedoProcessor::TextEditPreview(TextEditPreviewStruct& arguments) + { + } + + void GuiTextBoxUndoRedoProcessor::TextEditNotify(const TextEditNotifyStruct& arguments) + { + Ptr step=new EditStep; + step->processor=this; + step->arguments=arguments; + PushStep(step); + } + + void GuiTextBoxUndoRedoProcessor::TextCaretChanged(const TextCaretChangedStruct& arguments) + { + } + + void GuiTextBoxUndoRedoProcessor::TextEditFinished(vuint editVersion) + { + } + +/*********************************************************************** +GuiDocumentUndoRedoProcessor::ReplaceModelStep +***********************************************************************/ + + void GuiDocumentUndoRedoProcessor::ReplaceModelStep::Undo() + { + GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); + if(ci) + { + ci->EditRun(arguments.inputStart, arguments.inputEnd, arguments.originalModel, true); + ci->SetCaret(arguments.originalStart, arguments.originalEnd); + } + } + + void GuiDocumentUndoRedoProcessor::ReplaceModelStep::Redo() + { + GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); + if(ci) + { + ci->EditRun(arguments.originalStart, arguments.originalEnd, arguments.inputModel, true); + ci->SetCaret(arguments.inputStart, arguments.inputEnd); + } + } + +/*********************************************************************** +GuiDocumentUndoRedoProcessor::RenameStyleStep +***********************************************************************/ + + void GuiDocumentUndoRedoProcessor::RenameStyleStep::Undo() + { + GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); + if(ci) + { + ci->RenameStyle(arguments.newStyleName, arguments.oldStyleName); + } + } + + void GuiDocumentUndoRedoProcessor::RenameStyleStep::Redo() + { + GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); + if(ci) + { + ci->RenameStyle(arguments.oldStyleName, arguments.newStyleName); + } + } + +/*********************************************************************** +GuiDocumentUndoRedoProcessor::SetAlignmentStep +***********************************************************************/ + + void GuiDocumentUndoRedoProcessor::SetAlignmentStep::Undo() + { + GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); + if(ci) + { + ci->SetParagraphAlignments(TextPos(arguments->start, 0), TextPos(arguments->end, 0), arguments->originalAlignments); + } + } + + void GuiDocumentUndoRedoProcessor::SetAlignmentStep::Redo() + { + GuiDocumentCommonInterface* ci=dynamic_cast(processor->ownerComposition->GetRelatedControl()); + if(ci) + { + ci->SetParagraphAlignments(TextPos(arguments->start, 0), TextPos(arguments->end, 0), arguments->inputAlignments); + } + } + +/*********************************************************************** +GuiDocumentUndoRedoProcessor +***********************************************************************/ + + GuiDocumentUndoRedoProcessor::GuiDocumentUndoRedoProcessor() + :element(0) + ,ownerComposition(0) + { + } + + GuiDocumentUndoRedoProcessor::~GuiDocumentUndoRedoProcessor() + { + } + + void GuiDocumentUndoRedoProcessor::Setup(elements::GuiDocumentElement* _element, compositions::GuiGraphicsComposition* _ownerComposition) + { + element=_element; + ownerComposition=_ownerComposition; + } + + void GuiDocumentUndoRedoProcessor::OnReplaceModel(const ReplaceModelStruct& arguments) + { + Ptr step=new ReplaceModelStep; + step->processor=this; + step->arguments=arguments; + PushStep(step); + } + + void GuiDocumentUndoRedoProcessor::OnRenameStyle(const RenameStyleStruct& arguments) + { + Ptr step=new RenameStyleStep; + step->processor=this; + step->arguments=arguments; + PushStep(step); + } + + void GuiDocumentUndoRedoProcessor::OnSetAlignment(Ptr arguments) + { + Ptr step=new SetAlignmentStep; + step->processor=this; + step->arguments=arguments; + PushStep(step); + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEXTEDITORPACKAGE\LANGUAGESERVICE\GUILANGUAGEAUTOCOMPLETE.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace regex; + using namespace parsing; + using namespace parsing::tabling; + using namespace collections; + +/*********************************************************************** +GuiGrammarAutoComplete +***********************************************************************/ + + void GuiGrammarAutoComplete::Attach(elements::GuiColorizedTextElement* _element, SpinLock& _elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) + { + GuiTextBoxAutoCompleteBase::Attach(_element, _elementModifyLock, _ownerComposition, editVersion); + RepeatingParsingExecutor::CallbackBase::Attach(_element, _elementModifyLock, _ownerComposition, editVersion); + } + + void GuiGrammarAutoComplete::Detach() + { + GuiTextBoxAutoCompleteBase::Detach(); + RepeatingParsingExecutor::CallbackBase::Detach(); + if(element && elementModifyLock) + { + EnsureAutoCompleteFinished(); + } + } + + void GuiGrammarAutoComplete::TextEditPreview(TextEditPreviewStruct& arguments) + { + GuiTextBoxAutoCompleteBase::TextEditPreview(arguments); + RepeatingParsingExecutor::CallbackBase::TextEditPreview(arguments); + + if(element && elementModifyLock) + { + if(IsListOpening() && arguments.keyInput && arguments.originalText==L"" && arguments.inputText!=L"") + { + WString selectedItem=GetSelectedListItem(); + if(selectedItem!=L"") + { + TextPos begin=GetListStartPosition(); + TextPos end=arguments.originalStart; + WString editingText=element->GetLines().GetText(begin, end); + editingText+=arguments.inputText; + if(grammarParser->GetTable()->GetLexer().Walk().IsClosedToken(editingText)) + { + arguments.originalStart=begin; + arguments.inputText=selectedItem+arguments.inputText; + CloseList(); + } + } + } + } + } + + void GuiGrammarAutoComplete::TextEditNotify(const TextEditNotifyStruct& arguments) + { + GuiTextBoxAutoCompleteBase::TextEditNotify(arguments); + RepeatingParsingExecutor::CallbackBase::TextEditNotify(arguments); + if(element && elementModifyLock) + { + editing=true; + SPIN_LOCK(editTraceLock) + { + editTrace.Add(arguments); + } + } + } + + void GuiGrammarAutoComplete::TextCaretChanged(const TextCaretChangedStruct& arguments) + { + GuiTextBoxAutoCompleteBase::TextCaretChanged(arguments); + RepeatingParsingExecutor::CallbackBase::TextCaretChanged(arguments); + if(element && elementModifyLock) + { + SPIN_LOCK(editTraceLock) + { + // queue a fake TextEditNotifyStruct + // a fake struct can be detected by (trace.originalText==L"" && trace.inputText==L"") + TextEditNotifyStruct trace; + trace.editVersion=arguments.editVersion; + trace.originalStart=arguments.oldBegin; + trace.originalEnd=arguments.oldEnd; + trace.inputStart=arguments.newBegin; + trace.inputEnd=arguments.newEnd; + + // ensure trace.originalStart<=trace.originalEnd + if(trace.originalStart>trace.originalEnd) + { + TextPos temp=trace.originalStart; + trace.originalStart=trace.originalEnd; + trace.originalEnd=temp; + } + // ensure trace.inputStart<=trace.inputEnd + if(trace.inputStart>trace.inputEnd) + { + TextPos temp=trace.inputStart; + trace.inputStart=trace.inputEnd; + trace.inputEnd=temp; + } + editTrace.Add(trace); + } + + SPIN_LOCK(contextLock) + { + if(context.input.node) + { + if(editing) + { + // if the current caret changing is caused by editing + // submit a task with valid editVersion and invalid node and code + RepeatingParsingOutput input; + input.editVersion=context.input.editVersion; + SubmitTask(input); + } + else if(context.input.editVersion == arguments.editVersion) + { + // if the current caret changing is not caused by editing + // submit a task with the previous input + SubmitTask(context.input); + } + } + } + } + } + + void GuiGrammarAutoComplete::TextEditFinished(vuint editVersion) + { + GuiTextBoxAutoCompleteBase::TextEditFinished(editVersion); + RepeatingParsingExecutor::CallbackBase::TextEditFinished(editVersion); + if(element && elementModifyLock) + { + editing=false; + } + } + + void GuiGrammarAutoComplete::OnParsingFinishedAsync(const RepeatingParsingOutput& arguments) + { + if(element && elementModifyLock) + { + GetApplication()->InvokeInMainThread(ownerComposition->GetRelatedControlHost(), [=]() + { + // submit a task if the RepeatingParsingExecutor notices a new parsing result + SubmitTask(arguments); + }); + } + } + + void GuiGrammarAutoComplete::CollectLeftRecursiveRules() + { + leftRecursiveRules.Clear(); + Ptr parser=parsingExecutor->GetParser(); + Ptr table=parser->GetTable(); + vint stateCount=table->GetStateCount(); + vint tokenCount=table->GetTokenCount(); + for(vint i=0;i bag=table->GetTransitionBag(i, j); + if(bag) + { + FOREACH(Ptr, item, bag->transitionItems) + { + FOREACH(ParsingTable::Instruction, ins, item->instructions) + { + if(ins.instructionType==ParsingTable::Instruction::LeftRecursiveReduce) + { + if(!leftRecursiveRules.Contains(ins.creatorRule)) + { + leftRecursiveRules.Add(ins.creatorRule); + } + } + } + } + } + } + } + } + + vint GuiGrammarAutoComplete::UnsafeGetEditTraceIndex(vuint editVersion) + { + // get the index of the latest TextEditNotifyStruct of a specified edit version + // this function should be called inside SPIN_LOCK(editTraceLock) + // perform a binary search + vint start = 0; + vint end = editTrace.Count() - 1; + while (start <= end) + { + vint middle = (start + end) / 2; + TextEditNotifyStruct& trace = editTrace[middle]; + + if (editVersiontrace.editVersion) + { + start = middle + 1; + } + else + { + // if multiple TextEditNotifyStruct is found, choose the latest one + while (middle < editTrace.Count() - 1) + { + if (editTrace[middle + 1].editVersion == editTrace[middle].editVersion) + { + middle++; + } + else + { + break; + } + } + return middle; + } + } + return -1; + } + + TextPos GuiGrammarAutoComplete::ChooseCorrectTextPos(TextPos pos, const regex::RegexTokens& tokens) + { + Ptr table=grammarParser->GetTable(); + RegexToken lastToken; + lastToken.reading=0; + + FOREACH(RegexToken, token, tokens) + { + // we treat "class| Name" as editing the first token + if(TextPos(token.rowEnd, token.columnEnd+1)>=pos) + { + if(table->GetTableTokenIndex(token.token)!=-1 && lastToken.reading) + { + pos=TextPos(lastToken.rowStart, lastToken.columnStart); + } + break; + } + lastToken=token; + } + return pos; + } + + void GuiGrammarAutoComplete::ExecuteRefresh(AutoCompleteContext& newContext) + { + // process the input of a task is submitted not by text editing + // find the text selection by the edit version of the input + TextPos startPos, endPos; + { + SPIN_LOCK(editTraceLock) + { + vint traceIndex = UnsafeGetEditTraceIndex(newContext.input.editVersion); + if (traceIndex == -1) return; + + TextEditNotifyStruct& trace = editTrace[traceIndex]; + startPos = trace.inputStart; + endPos = trace.inputEnd; + } + + const RegexLexer& lexer = grammarParser->GetTable()->GetLexer(); + RegexTokens tokens = lexer.Parse(newContext.input.code); + startPos = ChooseCorrectTextPos(startPos, tokens); + } + + // locate the deepest node using the text selection + ParsingTextPos start(startPos.row, startPos.column); + ParsingTextPos end(endPos.row, endPos.column); + ParsingTextRange range(start, end); + ParsingTreeNode* found = newContext.input.node->FindDeepestNode(range); + ParsingTreeObject* selectedNode = 0; + + // if the location failed, choose the root node + if (!found || startPos == TextPos(0, 0)) + { + found = newContext.input.node.Obj(); + } + + if (!selectedNode) + { + // from the deepest node, traverse towards the root node + // find the deepest node whose created rule is a left recursive rule and whose parent is not + ParsingTreeObject* lrec = 0; + ParsingTreeNode* current = found; + while (current) + { + ParsingTreeObject* obj = dynamic_cast(current); + if (obj) + { + FOREACH(WString, rule, obj->GetCreatorRules()) + { + if (leftRecursiveRules.Contains(rule)) + { + lrec = obj; + break; + } + } + if (obj && lrec && lrec != obj) + { + selectedNode = lrec; + break; + } + } + current = current->GetParent(); + } + } + + if (!selectedNode) + { + // if there is no left recursive rule that creates the deepest node and all indirect parents + // choose the deepest ParsingTreeObject + ParsingTreeNode* current = found; + while (current) + { + ParsingTreeObject* obj = dynamic_cast(current); + if (obj) + { + selectedNode = obj; + break; + } + current = current->GetParent(); + } + } + + if (selectedNode) + { + // get the code range of the selected node + start = selectedNode->GetCodeRange().start; + end = selectedNode->GetCodeRange().end; + + // get all properties from the selected node + newContext.rule = selectedNode->GetCreatorRules()[selectedNode->GetCreatorRules().Count() - 1]; + newContext.originalRange = selectedNode->GetCodeRange(); + newContext.originalNode = dynamic_cast(selectedNode); + newContext.modifiedNode = newContext.originalNode; + newContext.modifiedEditVersion = newContext.input.editVersion; + + // get the corresponding code of the selected node + if (start.index >= 0 && end.index >= 0) + { + newContext.modifiedCode = newContext.input.code.Sub(start.index, end.index - start.index + 1).Buffer(); + } + } + } + + bool GuiGrammarAutoComplete::NormalizeTextPos(AutoCompleteContext& newContext, elements::text::TextLines& lines, TextPos& pos) + { + // get the start position + TextPos start(newContext.originalRange.start.row, newContext.originalRange.start.column); + + // get the end position of the end of lines + TextPos end + = lines.GetCount() <= 1 + ? TextPos(start.row, start.column + lines.GetLine(0).dataLength) + : TextPos(start.row + lines.GetCount() - 1, lines.GetLine(lines.GetCount() - 1).dataLength) + ; + + if (start <= pos && pos <= end) + { + // if the pos is inside the range + // normalize the pos to a new coordinate that the beginning position of lines is (row=0, column=0) + pos.row -= start.row; + if (pos.row == 0) + { + pos.column -= start.column; + } + return true; + } + else + { + return false; + } + } + + void GuiGrammarAutoComplete::ExecuteEdit(AutoCompleteContext& newContext) + { + // process the input of a task that is submitted by text editing + // this function make an approximiation to the context if the RepeatingParsingExecutor is not fast enough + // copy all TextEditNotifyStruct that is caused by a text editing before (and including) the edit version of the input + List usedTrace; + { + SPIN_LOCK(editTraceLock) + { + CopyFrom( + usedTrace, + From(editTrace) + .Where([&newContext](const TextEditNotifyStruct& value) + { + return (value.originalText != L"" || value.inputText != L"") && value.editVersion > newContext.modifiedEditVersion; + }) + ); + } + } + + // apply all modification to get the new modifiedCode + bool failed = false; + if (usedTrace.Count() > 0) + { + if (usedTrace[0].editVersion != newContext.modifiedEditVersion + 1) + { + // failed if any TextEditNotifyStruct is missing + failed = true; + } + else + { + // initialize a TextLines with the latest modifiedCode + text::TextLines lines(nullptr); + lines.SetText(newContext.modifiedCode); + FOREACH(TextEditNotifyStruct, trace, usedTrace) + { + // apply a modification to lines + TextPos start = trace.originalStart; + TextPos end = trace.originalEnd; + + // only if the modification is meaningful + if (NormalizeTextPos(newContext, lines, start) && NormalizeTextPos(newContext, lines, end)) + { + lines.Modify(start, end, trace.inputText); + } + else + { + // otherwise, failed + failed = true; + break; + } + } + + if (!failed) + { + newContext.modifiedCode = lines.GetText(); + } + } + } + + if (failed) + { + // clear originalNode to notify that the current context goes wrong + newContext.originalNode = 0; + } + + if (usedTrace.Count() > 0) + { + // update the edit version + newContext.modifiedEditVersion = usedTrace[usedTrace.Count() - 1].editVersion; + } + } + + void GuiGrammarAutoComplete::DeleteFutures(collections::List& futures) + { + // delete all futures and clear the list + FOREACH(ParsingState::Future*, future, futures) + { + delete future; + } + futures.Clear(); + } + + regex::RegexToken* GuiGrammarAutoComplete::TraverseTransitions( + parsing::tabling::ParsingState& state, + parsing::tabling::ParsingTransitionCollector& transitionCollector, + TextPos stopPosition, + collections::List& nonRecoveryFutures, + collections::List& recoveryFutures + ) + { + const List& transitions = transitionCollector.GetTransitions(); + for (vint index = 0; index < transitions.Count(); index++) + { + const ParsingState::TransitionResult& transition = transitions[index]; + switch (transition.transitionType) + { + case ParsingState::TransitionResult::AmbiguityBegin: + break; + case ParsingState::TransitionResult::AmbiguityBranch: + // ambiguity branches are not nested + // tokens in different braches are the same + // so we only need to run one branch, and skip the others + index = transitionCollector.GetAmbiguityEndFromBegin(transitionCollector.GetAmbiguityBeginFromBranch(index)); + break; + case ParsingState::TransitionResult::AmbiguityEnd: + break; + case ParsingState::TransitionResult::ExecuteInstructions: + { + // test does the token reach the stop position + if (transition.token) + { + // we treat "A|B" as editing A if token A is endless, otherwise treated as editing B + TextPos tokenEnd(transition.token->rowEnd, transition.token->columnEnd + 1); + + // if the caret is not at the end of the token + if (tokenEnd > stopPosition) + { + // stop the traversing and return the editing token + return transition.token; + } + else if (tokenEnd == stopPosition) + { + // if the caret is at the end of the token, and it is a closed token + // e.g. identifier is not a closed token, string is a closed token + if (!grammarParser->GetTable()->GetLexer().Walk().IsClosedToken(transition.token->reading, transition.token->length)) + { + // stop the traversing and return the editing token + return transition.token; + } + } + } + + // traverse the PDA using the token specified in the current transition + vint tableTokenIndex = transition.tableTokenIndex; + List possibilities; + if (recoveryFutures.Count() > 0) + { + FOREACH(ParsingState::Future*, future, recoveryFutures) + { + state.Explore(tableTokenIndex, future, possibilities); + } + } + else + { + FOREACH(ParsingState::Future*, future, nonRecoveryFutures) + { + state.Explore(tableTokenIndex, future, possibilities); + } + } + + // delete duplicated futures + List selectedPossibilities; + for (vint i = 0; i < possibilities.Count(); i++) + { + ParsingState::Future* candidateFuture = possibilities[i]; + bool duplicated = false; + FOREACH(ParsingState::Future*, future, selectedPossibilities) + { + if ( + candidateFuture->currentState == future->currentState && + candidateFuture->reduceStateCount == future->reduceStateCount && + candidateFuture->shiftStates.Count() == future->shiftStates.Count() + ) + { + bool same = true; + for (vint j = 0; j < future->shiftStates.Count(); j++) + { + if (candidateFuture->shiftStates[i] != future->shiftStates[i]) + { + same = false; + break; + } + } + + if ((duplicated = same)) + { + break; + } + } + } + + if (duplicated) + { + delete candidateFuture; + } + else + { + selectedPossibilities.Add(candidateFuture); + } + } + + // step forward + if (transition.token || transition.tableTokenIndex == ParsingTable::TokenBegin) + { + DeleteFutures(nonRecoveryFutures); + DeleteFutures(recoveryFutures); + CopyFrom(nonRecoveryFutures, selectedPossibilities); + } + else + { + DeleteFutures(recoveryFutures); + CopyFrom(recoveryFutures, selectedPossibilities); + } + } + break; + default:; + } + } + return 0; + } + + regex::RegexToken* GuiGrammarAutoComplete::SearchValidInputToken( + parsing::tabling::ParsingState& state, + parsing::tabling::ParsingTransitionCollector& transitionCollector, + TextPos stopPosition, + AutoCompleteContext& newContext, + collections::SortedList& tableTokenIndices + ) + { + // initialize the PDA state + state.Reset(newContext.rule); + List nonRecoveryFutures, recoveryFutures; + nonRecoveryFutures.Add(state.ExploreCreateRootFuture()); + + // traverse the PDA until it reach the stop position + // nonRecoveryFutures store the state when the last token (existing) is reached + // recoveryFutures store the state when the last token (inserted by error recovery) is reached + RegexToken* token = TraverseTransitions(state, transitionCollector, stopPosition, nonRecoveryFutures, recoveryFutures); + + // explore all possibilities from the last token before the stop position + List possibilities; + for (vint i = 0; i < nonRecoveryFutures.Count(); i++) + { + state.Explore(ParsingTable::NormalReduce, nonRecoveryFutures[i], nonRecoveryFutures); + state.Explore(ParsingTable::LeftRecursiveReduce, nonRecoveryFutures[i], nonRecoveryFutures); + } + FOREACH(ParsingState::Future*, future, nonRecoveryFutures) + { + vint count = state.GetTable()->GetTokenCount(); + for (vint i = ParsingTable::UserTokenStart; i < count; i++) + { + state.Explore(i, future, possibilities); + } + } + + // get all possible tokens that marked using @AutoCompleteCandidate + FOREACH(ParsingState::Future*, future, possibilities) + { + if (!tableTokenIndices.Contains(future->selectedToken)) + { + tableTokenIndices.Add(future->selectedToken); + } + } + + // release all data + DeleteFutures(possibilities); + DeleteFutures(nonRecoveryFutures); + DeleteFutures(recoveryFutures); + + // return the editing token + return token; + } + + TextPos GuiGrammarAutoComplete::GlobalTextPosToModifiedTextPos(AutoCompleteContext& newContext, TextPos pos) + { + pos.row-=newContext.originalRange.start.row; + if(pos.row==0) + { + pos.column-=newContext.originalRange.start.column; + } + return pos; + } + + TextPos GuiGrammarAutoComplete::ModifiedTextPosToGlobalTextPos(AutoCompleteContext& newContext, TextPos pos) + { + if(pos.row==0) + { + pos.column+=newContext.originalRange.start.column; + } + pos.row+=newContext.originalRange.start.row; + return pos; + } + + void GuiGrammarAutoComplete::ExecuteCalculateList(AutoCompleteContext& newContext) + { + // calcuate the content of the auto complete list + // it is sad that, because the parser's algorithm is too complex + // we need to reparse and track the internal state of the PDA(push-down automaton) here. + // initialize the PDA + ParsingState state(newContext.modifiedCode, grammarParser->GetTable()); + state.Reset(newContext.rule); + + // prepare to get all transitions + ParsingTransitionCollector collector; + List> errors; + + // reparse and get all transitions during parsing + if (grammarParser->Parse(state, collector, errors)) + { + // if modifiedNode is not prepared (the task is submitted because of text editing) + // use the transition to build the syntax tree + if (!newContext.modifiedNode) + { + ParsingTreeBuilder builder; + builder.Reset(); + bool succeeded = true; + FOREACH(ParsingState::TransitionResult, transition, collector.GetTransitions()) + { + if (!(succeeded = builder.Run(transition))) + { + break; + } + } + + if (succeeded) + { + Ptr parsedNode = builder.GetNode(); + newContext.modifiedNode = parsedNode.Cast(); + newContext.modifiedNode->InitializeQueryCache(); + } + } + + if (newContext.modifiedNode) + { + // get the latest text editing trace + TextEditNotifyStruct trace; + SPIN_LOCK(editTraceLock) + { + vint index = UnsafeGetEditTraceIndex(newContext.modifiedEditVersion); + if (index == -1) + { + return; + } + else + { + trace = editTrace[index]; + } + } + + // calculate the stop position for PDA traversing + TextPos stopPosition = GlobalTextPosToModifiedTextPos(newContext, trace.inputStart); + + // find all possible token before the current caret using the PDA + Ptr autoComplete = new AutoCompleteData; + SortedList tableTokenIndices; + RegexToken* editingToken = SearchValidInputToken(state, collector, stopPosition, newContext, tableTokenIndices); + + // collect all auto complete types + { + // collect all keywords that can be put into the auto complete list + FOREACH(vint, token, tableTokenIndices) + { + vint regexToken = token - ParsingTable::UserTokenStart; + if (regexToken >= 0) + { + autoComplete->candidates.Add(regexToken); + if (parsingExecutor->GetTokenMetaData(regexToken).isCandidate) + { + autoComplete->shownCandidates.Add(regexToken); + } + } + } + + // calculate the arranged stopPosition + if (editingToken) + { + TextPos tokenPos(editingToken->rowStart, editingToken->columnStart); + if (tokenPos < stopPosition) + { + stopPosition = tokenPos; + } + } + + // calculate the start/end position for PDA traversing + TextPos startPos, endPos; + { + startPos = ModifiedTextPosToGlobalTextPos(newContext, stopPosition); + autoComplete->startPosition = startPos; + endPos = trace.inputEnd; + if (newContext.modifiedNode != newContext.originalNode) + { + startPos = GlobalTextPosToModifiedTextPos(newContext, startPos); + endPos = GlobalTextPosToModifiedTextPos(newContext, endPos); + } + if (startPos0) + { + endPos.column--; + } + } + + // calculate the auto complete type + if (editingToken && parsingExecutor->GetTokenMetaData(editingToken->token).hasAutoComplete) + { + ParsingTextRange range(ParsingTextPos(startPos.row, startPos.column), ParsingTextPos(endPos.row, endPos.column)); + AutoCompleteData::RetriveContext(*autoComplete.Obj(), range, newContext.modifiedNode.Obj(), parsingExecutor.Obj()); + } + } + newContext.autoComplete = autoComplete; + } + } + } + + void GuiGrammarAutoComplete::Execute(const RepeatingParsingOutput& input) + { + SPIN_LOCK(contextLock) + { + if(input.editVersionInvokeInMainThread(ownerComposition->GetRelatedControlHost(), [=]() + { + PostList(newContext, byGlobalCorrection); + }); + } + } + + void GuiGrammarAutoComplete::PostList(const AutoCompleteContext& newContext, bool byGlobalCorrection) + { + bool openList = true; // true: make the list visible + bool keepListState = false; // true: don't change the list visibility + Ptr autoComplete = newContext.autoComplete; + + // if failed to get the auto complete list, close + if (!autoComplete) + { + openList = false; + } + if (openList) + { + if (autoComplete->shownCandidates.Count() + autoComplete->candidateItems.Count() == 0) + { + openList = false; + } + } + + TextPos startPosition, endPosition; + WString editingText; + if (openList) + { + SPIN_LOCK(editTraceLock) + { + // if the edit version is invalid, cancel + vint traceIndex = UnsafeGetEditTraceIndex(newContext.modifiedEditVersion); + if (traceIndex == -1) + { + return; + } + // an edit version has two trace at most, for text change and caret change, here we peak the text change + if (traceIndex > 0 && editTrace[traceIndex - 1].editVersion == context.modifiedEditVersion) + { + traceIndex--; + } + // if the edit version is not created by keyboard input, close + if (traceIndex >= 0) + { + TextEditNotifyStruct& trace = editTrace[traceIndex]; + if (!trace.keyInput) + { + openList = false; + } + } + + // scan all traces from the calculation's edit version until now + if (openList) + { + keepListState = true; + startPosition = autoComplete->startPosition; + endPosition = editTrace[editTrace.Count() - 1].inputEnd; + for (vint i = traceIndex; i < editTrace.Count(); i++) + { + TextEditNotifyStruct& trace = editTrace[i]; + // if there are no text change trace until now, don't change the list + if (trace.originalText != L"" || trace.inputText != L"") + { + keepListState = false; + } + // if the edit position goes before the start position of the auto complete, refresh + if (trace.inputEnd <= startPosition) + { + openList = false; + break; + } + } + } + + if (traceIndex > 0) + { + editTrace.RemoveRange(0, traceIndex); + } + } + } + + // if there is a global correction send to the UI thread but the list is not opening, cancel + if (byGlobalCorrection && !IsListOpening()) + { + return; + } + + // if the input text from the start position to the current position crosses a token, close + if (openList && element) + { + editingText = element->GetLines().GetText(startPosition, endPosition); + if (grammarParser->GetTable()->GetLexer().Walk().IsClosedToken(editingText)) + { + openList = false; + } + } + + // calculate the content of the list + if (autoComplete && ((!keepListState && openList) || IsListOpening())) + { + SortedList itemKeys; + List itemValues; + + // copy all candidate keywords + FOREACH(vint, token, autoComplete->shownCandidates) + { + WString literal = parsingExecutor->GetTokenMetaData(token).unescapedRegexText; + if (literal != L"" && !itemKeys.Contains(literal)) + { + ParsingCandidateItem item; + item.name = literal; + item.semanticId = -1; + itemValues.Insert(itemKeys.Add(literal), item); + } + } + + // copy all candidate symbols + if (autoComplete->acceptableSemanticIds) + { + FOREACH(ParsingCandidateItem, item, autoComplete->candidateItems) + { + if (autoComplete->acceptableSemanticIds->Contains(item.semanticId)) + { + // add all acceptable display of a symbol + // because a symbol can has multiple representation in different places + if (item.name != L"" && !itemKeys.Contains(item.name)) + { + itemValues.Insert(itemKeys.Add(item.name), item); + } + } + } + } + + // fill the list + List candidateItems; + for (vint i = 0; i < itemValues.Count(); i++) + { + auto& item = itemValues[i]; + if (item.tag.IsNull()) + { + if (auto analyzer = parsingExecutor->GetAnalyzer()) + { + item.tag = analyzer->CreateTagForCandidateItem(item); + } + } + + GuiTextBoxAutoCompleteBase::AutoCompleteItem candidateItem; + candidateItem.text = item.name; + candidateItem.tag = item.tag; + candidateItems.Add(candidateItem); + } + SetListContent(candidateItems); + } + + // set the list state + if (!keepListState) + { + if (openList) + { + OpenList(startPosition); + } + else + { + CloseList(); + } + } + + if (IsListOpening()) + { + HighlightList(editingText); + } + } + + void GuiGrammarAutoComplete::Initialize() + { + grammarParser=CreateAutoRecoverParser(parsingExecutor->GetParser()->GetTable()); + CollectLeftRecursiveRules(); + parsingExecutor->AttachCallback(this); + } + + void GuiGrammarAutoComplete::OnContextFinishedAsync(AutoCompleteContext& context) + { + if (auto analyzer = parsingExecutor->GetAnalyzer()) + { + if (context.autoComplete && context.autoComplete->acceptableSemanticIds) + { + analyzer->GetCandidateItemsAsync(*context.autoComplete.Obj(), context, context.autoComplete->candidateItems); + } + } + } + + void GuiGrammarAutoComplete::EnsureAutoCompleteFinished() + { + parsingExecutor->EnsureTaskFinished(); + SPIN_LOCK(contextLock) + { + context = AutoCompleteContext(); + } + } + + GuiGrammarAutoComplete::GuiGrammarAutoComplete(Ptr _parsingExecutor) + :RepeatingParsingExecutor::CallbackBase(_parsingExecutor) + ,editing(false) + { + Initialize(); + } + + GuiGrammarAutoComplete::GuiGrammarAutoComplete(Ptr _grammarParser, const WString& _grammarRule) + :RepeatingParsingExecutor::CallbackBase(new RepeatingParsingExecutor(_grammarParser, _grammarRule)) + ,editing(false) + { + Initialize(); + } + + GuiGrammarAutoComplete::~GuiGrammarAutoComplete() + { + EnsureAutoCompleteFinished(); + parsingExecutor->DetachCallback(this); + } + + Ptr GuiGrammarAutoComplete::GetParsingExecutor() + { + return parsingExecutor; + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEXTEDITORPACKAGE\LANGUAGESERVICE\GUILANGUAGECOLORIZER.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace elements; + using namespace parsing; + using namespace parsing::tabling; + using namespace collections; + using namespace theme; + +/*********************************************************************** +GuiGrammarColorizer +***********************************************************************/ + + void GuiGrammarColorizer::OnParsingFinishedAsync(const RepeatingParsingOutput& output) + { + SPIN_LOCK(contextLock) + { + context=output; + OnContextFinishedAsync(context); + } + RestartColorizer(); + } + + void GuiGrammarColorizer::OnContextFinishedAsync(const RepeatingParsingOutput& context) + { + } + + void GuiGrammarColorizer::Attach(elements::GuiColorizedTextElement* _element, SpinLock& _elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) + { + GuiTextBoxRegexColorizer::Attach(_element, _elementModifyLock, _ownerComposition, editVersion); + RepeatingParsingExecutor::CallbackBase::Attach(_element, _elementModifyLock, _ownerComposition, editVersion); + } + + void GuiGrammarColorizer::Detach() + { + GuiTextBoxRegexColorizer::Detach(); + RepeatingParsingExecutor::CallbackBase::Detach(); + if(element && elementModifyLock) + { + parsingExecutor->EnsureTaskFinished(); + StopColorizer(false); + } + } + + void GuiGrammarColorizer::TextEditPreview(TextEditPreviewStruct& arguments) + { + GuiTextBoxRegexColorizer::TextEditPreview(arguments); + RepeatingParsingExecutor::CallbackBase::TextEditPreview(arguments); + } + + void GuiGrammarColorizer::TextEditNotify(const TextEditNotifyStruct& arguments) + { + GuiTextBoxRegexColorizer::TextEditNotify(arguments); + RepeatingParsingExecutor::CallbackBase::TextEditNotify(arguments); + } + + void GuiGrammarColorizer::TextCaretChanged(const TextCaretChangedStruct& arguments) + { + GuiTextBoxRegexColorizer::TextCaretChanged(arguments); + RepeatingParsingExecutor::CallbackBase::TextCaretChanged(arguments); + } + + void GuiGrammarColorizer::TextEditFinished(vuint editVersion) + { + GuiTextBoxRegexColorizer::TextEditFinished(editVersion); + RepeatingParsingExecutor::CallbackBase::TextEditFinished(editVersion); + } + + void GuiGrammarColorizer::OnSemanticColorize(SemanticColorizeContext& context, const RepeatingParsingOutput& input) + { + if (auto analyzer = parsingExecutor->GetAnalyzer()) + { + auto semanticId = analyzer->GetSemanticIdForTokenAsync(context, input); + if(semanticId!=-1) + { + context.semanticId=semanticId; + } + } + } + + void GuiGrammarColorizer::EnsureColorizerFinished() + { + parsingExecutor->EnsureTaskFinished(); + StopColorizerForever(); + SPIN_LOCK(contextLock) + { + context=RepeatingParsingOutput(); + } + } + + GuiGrammarColorizer::GuiGrammarColorizer(Ptr _parsingExecutor) + :RepeatingParsingExecutor::CallbackBase(_parsingExecutor) + { + parsingExecutor->AttachCallback(this); + BeginSetColors(); + } + + GuiGrammarColorizer::GuiGrammarColorizer(Ptr _grammarParser, const WString& _grammarRule) + :RepeatingParsingExecutor::CallbackBase(new RepeatingParsingExecutor(_grammarParser, _grammarRule)) + { + parsingExecutor->AttachCallback(this); + BeginSetColors(); + } + + GuiGrammarColorizer::~GuiGrammarColorizer() + { + EnsureColorizerFinished(); + parsingExecutor->DetachCallback(this); + } + + void GuiGrammarColorizer::BeginSetColors() + { + ClearTokens(); + colorSettings.Clear(); + + text::ColorEntry entry; + { + entry.normal.text = Color(0, 0, 0); + entry.normal.background = Color(0, 0, 0, 0); + entry.selectedFocused.text = Color(255, 255, 255); + entry.selectedFocused.background = Color(51, 153, 255); + entry.selectedUnfocused.text = Color(255, 255, 255); + entry.selectedUnfocused.background = Color(51, 153, 255); + } + + SetDefaultColor(entry); + colorSettings.Add(L"Default", entry); + } + + const collections::SortedList& GuiGrammarColorizer::GetColorNames() + { + return colorSettings.Keys(); + } + + GuiGrammarColorizer::ColorEntry GuiGrammarColorizer::GetColor(const WString& name) + { + vint index=colorSettings.Keys().IndexOf(name); + return index==-1?GetDefaultColor():colorSettings.Values().Get(index); + } + + void GuiGrammarColorizer::SetColor(const WString& name, const ColorEntry& entry) + { + colorSettings.Set(name, entry); + } + + void GuiGrammarColorizer::SetColor(const WString& name, const Color& color) + { + text::ColorEntry entry=GetDefaultColor(); + entry.normal.text=color; + SetColor(name, entry); + } + + void GuiGrammarColorizer::EndSetColors() + { + SortedList tokenColors; + Ptr table=parsingExecutor->GetParser()->GetTable(); + semanticColorMap.Clear(); + + vint tokenCount=table->GetTokenCount(); + for(vint token=ParsingTable::UserTokenStart;tokenGetTokenInfo(token); + const RepeatingParsingExecutor::TokenMetaData& md=parsingExecutor->GetTokenMetaData(token-ParsingTable::UserTokenStart); + if(md.defaultColorIndex==-1) + { + AddToken(tokenInfo.regex, GetDefaultColor()); + } + else + { + WString name=parsingExecutor->GetSemanticName(md.defaultColorIndex); + vint color=AddToken(tokenInfo.regex, GetColor(name)); + semanticColorMap.Set(md.defaultColorIndex, color); + tokenColors.Add(name); + } + } + + FOREACH_INDEXER(WString, color, index, colorSettings.Keys()) + { + if(!tokenColors.Contains(color)) + { + vint semanticId=parsingExecutor->GetSemanticId(color); + if(semanticId!=-1) + { + vint tokenId=AddExtraToken(colorSettings.Values().Get(index)); + vint color=tokenId+tokenCount-ParsingTable::UserTokenStart; + semanticColorMap.Set(semanticId, color); + } + } + } + Setup(); + } + + void GuiGrammarColorizer::ColorizeTokenContextSensitive(vint lineIndex, const wchar_t* text, vint start, vint length, vint& token, vint& contextState) + { + SPIN_LOCK(contextLock) + { + ParsingTreeObject* node=context.node.Obj(); + if(node && token!=-1 && parsingExecutor->GetTokenMetaData(token).hasContextColor) + { + ParsingTextPos pos(lineIndex, start); + SemanticColorizeContext scContext; + if(SemanticColorizeContext::RetriveContext(scContext, pos, node, parsingExecutor.Obj())) + { + const RepeatingParsingExecutor::FieldMetaData& md=parsingExecutor->GetFieldMetaData(scContext.type, scContext.field); + vint semantic=md.colorIndex; + scContext.semanticId=-1; + + if(scContext.acceptableSemanticIds) + { + OnSemanticColorize(scContext, context); + if(md.semantics->Contains(scContext.semanticId)) + { + semantic=scContext.semanticId; + } + } + + if(semantic!=-1) + { + vint index=semanticColorMap.Keys().IndexOf(semantic); + if(index!=-1) + { + token=semanticColorMap.Values()[index]; + } + } + } + } + } + } + + Ptr GuiGrammarColorizer::GetParsingExecutor() + { + return parsingExecutor; + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TEXTEDITORPACKAGE\LANGUAGESERVICE\GUILANGUAGEOPERATIONS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace parsing; + using namespace parsing::tabling; + using namespace regex_internal; + +/*********************************************************************** +ParsingContext +***********************************************************************/ + + bool ParsingTokenContext::RetriveContext(ParsingTokenContext& output, parsing::ParsingTreeNode* foundNode, RepeatingParsingExecutor* executor) + { + ParsingTreeToken* foundToken=dynamic_cast(foundNode); + if(!foundToken) return false; + ParsingTreeObject* tokenParent=dynamic_cast(foundNode->GetParent()); + if(!tokenParent) return false; + vint index=tokenParent->GetMembers().Values().IndexOf(foundNode); + if(index==-1) return false; + + WString type=tokenParent->GetType(); + WString field=tokenParent->GetMembers().Keys().Get(index); + const RepeatingParsingExecutor::FieldMetaData& md=executor->GetFieldMetaData(type, field); + + output.foundToken=foundToken; + output.tokenParent=tokenParent; + output.type=type; + output.field=field; + output.acceptableSemanticIds=md.semantics; + return true; + } + + bool ParsingTokenContext::RetriveContext(ParsingTokenContext& output, parsing::ParsingTextPos pos, parsing::ParsingTreeObject* rootNode, RepeatingParsingExecutor* executor) + { + ParsingTreeNode* foundNode=rootNode->FindDeepestNode(pos); + if(!foundNode) return false; + return RetriveContext(output, foundNode, executor); + } + + bool ParsingTokenContext::RetriveContext(ParsingTokenContext& output, parsing::ParsingTextRange range, ParsingTreeObject* rootNode, RepeatingParsingExecutor* executor) + { + ParsingTreeNode* foundNode=rootNode->FindDeepestNode(range); + if(!foundNode) return false; + return RetriveContext(output, foundNode, executor); + } + +/*********************************************************************** +RepeatingParsingExecutor::IParsingAnalyzer +***********************************************************************/ + + parsing::ParsingTreeNode* RepeatingParsingExecutor::IParsingAnalyzer::ToParent(parsing::ParsingTreeNode* node, const RepeatingPartialParsingOutput* output) + { + if (!output || !output->modifiedNode) return node; + return node == output->modifiedNode.Obj() + ? output->originalNode.Obj() + : node; + } + + parsing::ParsingTreeObject* RepeatingParsingExecutor::IParsingAnalyzer::ToChild(parsing::ParsingTreeObject* node, const RepeatingPartialParsingOutput* output) + { + if (!output || !output->modifiedNode) return node; + return node == output->originalNode.Obj() + ? output->modifiedNode.Obj() + : node; + } + + Ptr RepeatingParsingExecutor::IParsingAnalyzer::ToChild(Ptr node, const RepeatingPartialParsingOutput* output) + { + if (!output) return node; + return node == output->originalNode + ? output->modifiedNode.Cast() + : node; + } + + parsing::ParsingTreeNode* RepeatingParsingExecutor::IParsingAnalyzer::GetParent(parsing::ParsingTreeNode* node, const RepeatingPartialParsingOutput* output) + { + return ToParent(node, output)->GetParent(); + } + + Ptr RepeatingParsingExecutor::IParsingAnalyzer::GetMember(parsing::ParsingTreeObject* node, const WString& name, const RepeatingPartialParsingOutput* output) + { + return ToChild(ToChild(node, output)->GetMember(name), output); + } + + Ptr RepeatingParsingExecutor::IParsingAnalyzer::GetItem(parsing::ParsingTreeArray* node, vint index, const RepeatingPartialParsingOutput* output) + { + return ToChild(node->GetItem(index), output); + } + +/*********************************************************************** +RepeatingParsingExecutor::CallbackBase +***********************************************************************/ + + RepeatingParsingExecutor::CallbackBase::CallbackBase(Ptr _parsingExecutor) + :parsingExecutor(_parsingExecutor) + ,callbackAutoPushing(false) + ,callbackElement(0) + ,callbackElementModifyLock(0) + { + } + + RepeatingParsingExecutor::CallbackBase::~CallbackBase() + { + } + + void RepeatingParsingExecutor::CallbackBase::RequireAutoSubmitTask(bool enabled) + { + callbackAutoPushing=enabled; + } + + void RepeatingParsingExecutor::CallbackBase::Attach(elements::GuiColorizedTextElement* _element, SpinLock& _elementModifyLock, compositions::GuiGraphicsComposition* _ownerComposition, vuint editVersion) + { + if(_element) + { + SPIN_LOCK(_elementModifyLock) + { + callbackElement=_element; + callbackElementModifyLock=&_elementModifyLock; + } + } + + parsingExecutor->ActivateCallback(this); + if(callbackElement && callbackElementModifyLock && callbackAutoPushing) + { + SPIN_LOCK(*callbackElementModifyLock) + { + RepeatingParsingInput input; + input.editVersion=editVersion; + input.code=callbackElement->GetLines().GetText(); + parsingExecutor->SubmitTask(input); + } + } + } + + void RepeatingParsingExecutor::CallbackBase::Detach() + { + if(callbackElement && callbackElementModifyLock) + { + SPIN_LOCK(*callbackElementModifyLock) + { + callbackElement=0; + callbackElementModifyLock=0; + } + } + + parsingExecutor->DeactivateCallback(this); + } + + void RepeatingParsingExecutor::CallbackBase::TextEditPreview(TextEditPreviewStruct& arguments) + { + } + + void RepeatingParsingExecutor::CallbackBase::TextEditNotify(const TextEditNotifyStruct& arguments) + { + } + + void RepeatingParsingExecutor::CallbackBase::TextCaretChanged(const TextCaretChangedStruct& arguments) + { + } + + void RepeatingParsingExecutor::CallbackBase::TextEditFinished(vuint editVersion) + { + if(callbackElement && callbackElementModifyLock && callbackAutoPushing) + { + SPIN_LOCK(*callbackElementModifyLock) + { + RepeatingParsingInput input; + input.editVersion=editVersion; + input.code=callbackElement->GetLines().GetText(); + parsingExecutor->SubmitTask(input); + } + } + } + +/*********************************************************************** +RepeatingParsingExecutor +***********************************************************************/ + + void RepeatingParsingExecutor::Execute(const RepeatingParsingInput& input) + { + List> errors; + Ptr node=grammarParser->Parse(input.code, grammarRule, errors).Cast(); + if(node) + { + node->InitializeQueryCache(); + } + + RepeatingParsingOutput result; + result.node=node; + result.editVersion=input.editVersion; + result.code=input.code; + if(node) + { + OnContextFinishedAsync(result); + FOREACH(ICallback*, callback, callbacks) + { + callback->OnParsingFinishedAsync(result); + } + } + } + + void RepeatingParsingExecutor::PrepareMetaData() + { + Ptr table=grammarParser->GetTable(); + tokenIndexMap.Clear(); + semanticIndexMap.Clear(); + tokenMetaDatas.Clear(); + fieldMetaDatas.Clear(); + + Dictionary> tokenColorAtts, tokenContextColorAtts, tokenCandidateAtts, tokenAutoCompleteAtts; + Dictionary> fieldColorAtts, fieldSemanticAtts; + + { + vint tokenCount=table->GetTokenCount(); + for(vint token=ParsingTable::UserTokenStart;tokenGetTokenInfo(token); + vint tokenIndex=token-ParsingTable::UserTokenStart; + tokenIndexMap.Add(tokenInfo.name, tokenIndex); + + if(Ptr att=GetColorAttribute(tokenInfo.attributeIndex)) + { + tokenColorAtts.Add(tokenIndex, att); + } + if(Ptr att=GetContextColorAttribute(tokenInfo.attributeIndex)) + { + tokenContextColorAtts.Add(tokenIndex, att); + } + if(Ptr att=GetCandidateAttribute(tokenInfo.attributeIndex)) + { + tokenCandidateAtts.Add(tokenIndex, att); + } + if(Ptr att=GetAutoCompleteAttribute(tokenInfo.attributeIndex)) + { + tokenAutoCompleteAtts.Add(tokenIndex, att); + } + } + } + { + vint fieldCount=table->GetTreeFieldInfoCount(); + for(vint field=0;fieldGetTreeFieldInfo(field); + FieldDesc fieldDesc(fieldInfo.type, fieldInfo.field); + + if(Ptr att=GetColorAttribute(fieldInfo.attributeIndex)) + { + fieldColorAtts.Add(fieldDesc, att); + } + if(Ptr att=GetSemanticAttribute(fieldInfo.attributeIndex)) + { + fieldSemanticAtts.Add(fieldDesc, att); + } + } + } + + FOREACH(Ptr, att, + From(tokenColorAtts.Values()) + .Concat(tokenContextColorAtts.Values()) + .Concat(fieldColorAtts.Values()) + .Concat(fieldSemanticAtts.Values()) + ) + { + FOREACH(WString, argument, att->arguments) + { + if(!semanticIndexMap.Contains(argument)) + { + semanticIndexMap.Add(argument); + } + } + } + + vint index=0; + FOREACH(vint, tokenIndex, tokenIndexMap.Values()) + { + TokenMetaData md; + md.tableTokenIndex=tokenIndex+ParsingTable::UserTokenStart; + md.lexerTokenIndex=tokenIndex; + md.defaultColorIndex=-1; + md.hasContextColor=false; + md.hasAutoComplete=false; + md.isCandidate=false; + + if((index=tokenColorAtts.Keys().IndexOf(tokenIndex))!=-1) + { + md.defaultColorIndex=semanticIndexMap.IndexOf(tokenColorAtts.Values()[index]->arguments[0]); + } + md.hasContextColor=tokenContextColorAtts.Keys().Contains(tokenIndex); + md.hasAutoComplete=tokenAutoCompleteAtts.Keys().Contains(tokenIndex); + if((md.isCandidate=tokenCandidateAtts.Keys().Contains(tokenIndex))) + { + const ParsingTable::TokenInfo& tokenInfo=table->GetTokenInfo(md.tableTokenIndex); + if(IsRegexEscapedLiteralString(tokenInfo.regex)) + { + md.unescapedRegexText=UnescapeTextForRegex(tokenInfo.regex); + } + else + { + md.isCandidate=false; + } + } + + tokenMetaDatas.Add(tokenIndex, md); + } + { + vint fieldCount=table->GetTreeFieldInfoCount(); + for(vint field=0;fieldGetTreeFieldInfo(field); + FieldDesc fieldDesc(fieldInfo.type, fieldInfo.field); + + FieldMetaData md; + md.colorIndex=-1; + + if((index=fieldColorAtts.Keys().IndexOf(fieldDesc))!=-1) + { + md.colorIndex=semanticIndexMap.IndexOf(fieldColorAtts.Values()[index]->arguments[0]); + } + if((index=fieldSemanticAtts.Keys().IndexOf(fieldDesc))!=-1) + { + md.semantics=new List; + FOREACH(WString, argument, fieldSemanticAtts.Values()[index]->arguments) + { + md.semantics->Add(semanticIndexMap.IndexOf(argument)); + } + } + + fieldMetaDatas.Add(fieldDesc, md); + } + } + } + + void RepeatingParsingExecutor::OnContextFinishedAsync(RepeatingParsingOutput& context) + { + if(analyzer) + { + context.cache = analyzer->CreateCacheAsync(context); + } + } + + RepeatingParsingExecutor::RepeatingParsingExecutor(Ptr _grammarParser, const WString& _grammarRule, Ptr _analyzer) + :grammarParser(_grammarParser) + ,grammarRule(_grammarRule) + ,analyzer(_analyzer) + ,autoPushingCallback(0) + { + PrepareMetaData(); + if (analyzer) + { + analyzer->Attach(this); + } + } + + RepeatingParsingExecutor::~RepeatingParsingExecutor() + { + EnsureTaskFinished(); + if (analyzer) + { + analyzer->Detach(this); + } + } + + Ptr RepeatingParsingExecutor::GetParser() + { + return grammarParser; + } + + bool RepeatingParsingExecutor::AttachCallback(ICallback* value) + { + if(!value) return false; + if(callbacks.Contains(value)) return false; + callbacks.Add(value); + return true; + } + + bool RepeatingParsingExecutor::DetachCallback(ICallback* value) + { + if(!value) return false; + if(!callbacks.Contains(value)) return false; + DeactivateCallback(value); + callbacks.Remove(value); + return true; + } + + bool RepeatingParsingExecutor::ActivateCallback(ICallback* value) + { + if(!value) return false; + if(!callbacks.Contains(value)) return false; + if(activatedCallbacks.Contains(value)) return false; + activatedCallbacks.Add(value); + + if(!autoPushingCallback) + { + autoPushingCallback=value; + autoPushingCallback->RequireAutoSubmitTask(true); + } + return true; + } + + bool RepeatingParsingExecutor::DeactivateCallback(ICallback* value) + { + if(!value) return false; + if(!callbacks.Contains(value)) return false; + if(!activatedCallbacks.Contains(value)) return false; + + if(autoPushingCallback==value) + { + autoPushingCallback->RequireAutoSubmitTask(false); + autoPushingCallback=0; + } + activatedCallbacks.Remove(value); + if(!autoPushingCallback && activatedCallbacks.Count()>0) + { + autoPushingCallback=activatedCallbacks[0]; + autoPushingCallback->RequireAutoSubmitTask(true); + } + return true; + } + + Ptr RepeatingParsingExecutor::GetAnalyzer() + { + return analyzer; + } + + vint RepeatingParsingExecutor::GetTokenIndex(const WString& tokenName) + { + vint index=tokenIndexMap.Keys().IndexOf(tokenName); + return index==-1?-1:tokenIndexMap.Values()[index]; + } + + vint RepeatingParsingExecutor::GetSemanticId(const WString& name) + { + return semanticIndexMap.IndexOf(name); + } + + WString RepeatingParsingExecutor::GetSemanticName(vint id) + { + return 0<=id&&id RepeatingParsingExecutor::GetAttribute(vint index, const WString& name, vint argumentCount) + { + if(index!=-1) + { + Ptr att=grammarParser->GetTable()->GetAttributeInfo(index)->FindFirst(name); + if(att && (argumentCount==-1 || att->arguments.Count()==argumentCount)) + { + return att; + } + } + return 0; + } + + Ptr RepeatingParsingExecutor::GetColorAttribute(vint index) + { + return GetAttribute(index, L"Color", 1); + } + + Ptr RepeatingParsingExecutor::GetContextColorAttribute(vint index) + { + return GetAttribute(index, L"ContextColor", 0); + } + + Ptr RepeatingParsingExecutor::GetSemanticAttribute(vint index) + { + return GetAttribute(index, L"Semantic", -1); + } + + Ptr RepeatingParsingExecutor::GetCandidateAttribute(vint index) + { + return GetAttribute(index, L"Candidate", 0); + } + + Ptr RepeatingParsingExecutor::GetAutoCompleteAttribute(vint index) + { + return GetAttribute(index, L"AutoComplete", 0); + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TOOLSTRIPPACKAGE\GUIMENUCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace compositions; + +/*********************************************************************** +IGuiMenuService +***********************************************************************/ + + const wchar_t* const IGuiMenuService::Identifier = L"vl::presentation::controls::IGuiMenuService"; + const wchar_t* const IGuiMenuDropdownProvider::Identifier = L"vl::presentation::controls::IGuiMenuDropdownProvider"; + + IGuiMenuService::IGuiMenuService() + :openingMenu(0) + { + } + + void IGuiMenuService::MenuItemExecuted() + { + if(openingMenu) + { + openingMenu->Hide(); + } + if(GetParentMenuService()) + { + GetParentMenuService()->MenuItemExecuted(); + } + } + + GuiMenu* IGuiMenuService::GetOpeningMenu() + { + return openingMenu; + } + + void IGuiMenuService::MenuOpened(GuiMenu* menu) + { + if(openingMenu!=menu && openingMenu) + { + openingMenu->Hide(); + } + openingMenu=menu; + } + + void IGuiMenuService::MenuClosed(GuiMenu* menu) + { + if(openingMenu==menu) + { + openingMenu=0; + } + } + +/*********************************************************************** +GuiMenu +***********************************************************************/ + + void GuiMenu::BeforeControlTemplateUninstalled_() + { + } + + void GuiMenu::AfterControlTemplateInstalled_(bool initialize) + { + } + + IGuiMenuService* GuiMenu::GetParentMenuService() + { + return parentMenuService; + } + + IGuiMenuService::Direction GuiMenu::GetPreferredDirection() + { + return IGuiMenuService::Vertical; + } + + bool GuiMenu::IsActiveState() + { + return true; + } + + bool GuiMenu::IsSubMenuActivatedByMouseDown() + { + return false; + } + + void GuiMenu::MenuItemExecuted() + { + IGuiMenuService::MenuItemExecuted(); + Hide(); + } + + void GuiMenu::OnWindowOpened(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(parentMenuService) + { + parentMenuService->MenuOpened(this); + } + } + + void GuiMenu::OnDeactivatedAltHost() + { + Hide(); + } + + void GuiMenu::MouseClickedOnOtherWindow(GuiWindow* window) + { + GuiMenu* targetMenu=dynamic_cast(window); + if(!targetMenu) + { + Hide(); + } + } + + void GuiMenu::OnWindowClosed(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(parentMenuService) + { + parentMenuService->MenuClosed(this); + GuiMenu* openingSubMenu=GetOpeningMenu(); + if(openingSubMenu) + { + openingSubMenu->Hide(); + } + } + } + + GuiMenu::GuiMenu(theme::ThemeName themeName, GuiControl* _owner) + :GuiPopup(themeName) + , owner(_owner) + , parentMenuService(0) + { + GetNativeWindow()->SetAlwaysPassFocusToParent(true); + UpdateMenuService(); + WindowOpened.AttachMethod(this, &GuiMenu::OnWindowOpened); + WindowClosed.AttachMethod(this, &GuiMenu::OnWindowClosed); + } + + GuiMenu::~GuiMenu() + { + } + + void GuiMenu::UpdateMenuService() + { + if(owner) + { + parentMenuService=owner->QueryTypedService(); + } + } + + IDescriptable* GuiMenu::QueryService(const WString& identifier) + { + if(identifier==IGuiMenuService::Identifier) + { + return (IGuiMenuService*)this; + } + else + { + return GuiPopup::QueryService(identifier); + } + } + +/*********************************************************************** +GuiMenuBar +***********************************************************************/ + + IGuiMenuService* GuiMenuBar::GetParentMenuService() + { + return 0; + } + + IGuiMenuService::Direction GuiMenuBar::GetPreferredDirection() + { + return IGuiMenuService::Horizontal; + } + + bool GuiMenuBar::IsActiveState() + { + return GetOpeningMenu()!=0; + } + + bool GuiMenuBar::IsSubMenuActivatedByMouseDown() + { + return true; + } + + GuiMenuBar::GuiMenuBar(theme::ThemeName themeName) + :GuiControl(themeName) + { + } + + GuiMenuBar::~GuiMenuBar() + { + } + + IDescriptable* GuiMenuBar::QueryService(const WString& identifier) + { + if(identifier==IGuiMenuService::Identifier) + { + return (IGuiMenuService*)this; + } + else + { + return GuiControl::QueryService(identifier); + } + } + +/*********************************************************************** +GuiMenuButton +***********************************************************************/ + + void GuiMenuButton::BeforeControlTemplateUninstalled_() + { + auto host = GetSubMenuHost(); + host->Clicked.Detach(hostClickedHandler); + host->GetBoundsComposition()->GetEventReceiver()->mouseEnter.Detach(hostMouseEnterHandler); + + hostClickedHandler = nullptr; + hostMouseEnterHandler = nullptr; + } + + void GuiMenuButton::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + auto host = GetSubMenuHost(); + + ct->SetSubMenuOpening(GetSubMenuOpening()); + ct->SetLargeImage(largeImage); + ct->SetImage(image); + ct->SetShortcutText(shortcutText); + ct->SetSubMenuExisting(subMenu != nullptr); + + hostClickedHandler = host->Clicked.AttachMethod(this, &GuiMenuButton::OnClicked); + hostMouseEnterHandler = host->GetBoundsComposition()->GetEventReceiver()->mouseEnter.AttachMethod(this, &GuiMenuButton::OnMouseEnter); + } + + GuiButton* GuiMenuButton::GetSubMenuHost() + { + GuiButton* button = GetControlTemplateObject(true)->GetSubMenuHost(); + return button ? button : this; + } + + void GuiMenuButton::OpenSubMenuInternal() + { + if(!GetSubMenuOpening()) + { + if(ownerMenuService) + { + GuiMenu* openingSiblingMenu=ownerMenuService->GetOpeningMenu(); + if(openingSiblingMenu) + { + openingSiblingMenu->Hide(); + } + } + SetSubMenuOpening(true); + } + } + + void GuiMenuButton::OnParentLineChanged() + { + GuiButton::OnParentLineChanged(); + ownerMenuService=QueryTypedService(); + if(ownerMenuService) + { + SetClickOnMouseUp(!ownerMenuService->IsSubMenuActivatedByMouseDown()); + } + if(subMenu) + { + subMenu->UpdateMenuService(); + } + } + + bool GuiMenuButton::IsAltAvailable() + { + return true; + } + + compositions::IGuiAltActionHost* GuiMenuButton::GetActivatingAltHost() + { + if (subMenu) + { + return subMenu->QueryTypedService(); + } + return 0; + } + + void GuiMenuButton::OnSubMenuWindowOpened(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + SubMenuOpeningChanged.Execute(GetNotifyEventArguments()); + GetControlTemplateObject(true)->SetSubMenuOpening(true); + } + + void GuiMenuButton::OnSubMenuWindowClosed(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + SubMenuOpeningChanged.Execute(GetNotifyEventArguments()); + GetControlTemplateObject(true)->SetSubMenuOpening(false); + } + + void GuiMenuButton::OnMouseEnter(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(GetVisuallyEnabled()) + { + if(cascadeAction && ownerMenuService && ownerMenuService->IsActiveState()) + { + OpenSubMenuInternal(); + } + } + } + + void GuiMenuButton::OnClicked(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(GetVisuallyEnabled()) + { + BeforeSubMenuOpening.Execute(GetNotifyEventArguments()); + if(GetSubMenu()) + { + OpenSubMenuInternal(); + } + else if(ownerMenuService) + { + ownerMenuService->MenuItemExecuted(); + } + } + } + + IGuiMenuService::Direction GuiMenuButton::GetSubMenuDirection() + { + return ownerMenuService?ownerMenuService->GetPreferredDirection():IGuiMenuService::Horizontal; + } + + GuiMenu* GuiMenuButton::ProvideDropdownMenu() + { + return GetSubMenu(); + } + + GuiMenuButton::GuiMenuButton(theme::ThemeName themeName) + :GuiSelectableButton(themeName) + ,subMenu(0) + ,ownedSubMenu(false) + ,ownerMenuService(0) + ,cascadeAction(true) + { + SetAutoSelection(false); + BeforeSubMenuOpening.SetAssociatedComposition(boundsComposition); + SubMenuOpeningChanged.SetAssociatedComposition(boundsComposition); + LargeImageChanged.SetAssociatedComposition(boundsComposition); + ImageChanged.SetAssociatedComposition(boundsComposition); + ShortcutTextChanged.SetAssociatedComposition(boundsComposition); + } + + GuiMenuButton::~GuiMenuButton() + { + if(subMenu && ownedSubMenu) + { + delete subMenu; + } + } + + Ptr GuiMenuButton::GetLargeImage() + { + return largeImage; + } + + void GuiMenuButton::SetLargeImage(Ptr value) + { + if (largeImage != value) + { + largeImage = value; + GetControlTemplateObject(true)->SetLargeImage(largeImage); + LargeImageChanged.Execute(GetNotifyEventArguments()); + } + } + + Ptr GuiMenuButton::GetImage() + { + return image; + } + + void GuiMenuButton::SetImage(Ptr value) + { + if (image != value) + { + image = value; + GetControlTemplateObject(true)->SetImage(image); + ImageChanged.Execute(GetNotifyEventArguments()); + } + } + + const WString& GuiMenuButton::GetShortcutText() + { + return shortcutText; + } + + void GuiMenuButton::SetShortcutText(const WString& value) + { + if (shortcutText != value) + { + shortcutText = value; + GetControlTemplateObject(true)->SetShortcutText(shortcutText); + ShortcutTextChanged.Execute(GetNotifyEventArguments()); + } + } + + bool GuiMenuButton::IsSubMenuExists() + { + return subMenu!=0; + } + + GuiMenu* GuiMenuButton::GetSubMenu() + { + return subMenu; + } + + GuiMenu* GuiMenuButton::CreateSubMenu(TemplateProperty subMenuTemplate) + { + if (!subMenu) + { + GuiMenu* newSubMenu = new GuiMenu(theme::ThemeName::Menu, this); + newSubMenu->SetControlTemplate(subMenuTemplate ? subMenuTemplate : GetControlTemplateObject(true)->GetSubMenuTemplate()); + SetSubMenu(newSubMenu, true); + } + return subMenu; + } + + void GuiMenuButton::SetSubMenu(GuiMenu* value, bool owned) + { + if(subMenu) + { + if(ownedSubMenu) + { + delete subMenu; + } + } + subMenu=value; + ownedSubMenu=owned; + if(subMenu) + { + subMenu->WindowOpened.AttachMethod(this, &GuiMenuButton::OnSubMenuWindowOpened); + subMenu->WindowClosed.AttachMethod(this, &GuiMenuButton::OnSubMenuWindowClosed); + } + GetControlTemplateObject(true)->SetSubMenuExisting(subMenu != nullptr); + } + + void GuiMenuButton::DestroySubMenu() + { + if(subMenu) + { + if(ownedSubMenu) + { + delete subMenu; + } + subMenu=0; + ownedSubMenu=false; + GetControlTemplateObject(true)->SetSubMenuExisting(false); + } + } + + bool GuiMenuButton::GetOwnedSubMenu() + { + return subMenu && ownedSubMenu; + } + + bool GuiMenuButton::GetSubMenuOpening() + { + if(subMenu) + { + return subMenu->GetOpening(); + } + else + { + return false; + } + } + + void GuiMenuButton::SetSubMenuOpening(bool value) + { + if(subMenu) + { + if(value) + { + subMenu->SetClientSize(preferredMenuClientSize); + IGuiMenuService::Direction direction=GetSubMenuDirection(); + subMenu->ShowPopup(GetSubMenuHost(), direction==IGuiMenuService::Horizontal); + } + else + { + subMenu->Close(); + } + } + } + + Size GuiMenuButton::GetPreferredMenuClientSize() + { + return preferredMenuClientSize; + } + + void GuiMenuButton::SetPreferredMenuClientSize(Size value) + { + preferredMenuClientSize=value; + } + + bool GuiMenuButton::GetCascadeAction() + { + return cascadeAction; + } + + void GuiMenuButton::SetCascadeAction(bool value) + { + cascadeAction=value; + } + + IDescriptable* GuiMenuButton::QueryService(const WString& identifier) + { + if (identifier == IGuiMenuDropdownProvider::Identifier) + { + return (IGuiMenuDropdownProvider*)this; + } + else + { + return GuiSelectableButton::QueryService(identifier); + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TOOLSTRIPPACKAGE\GUIRIBBONCONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace reflection::description; + using namespace collections; + using namespace compositions; + using namespace theme; + using namespace templates; + +/*********************************************************************** +GuiRibbonTab +***********************************************************************/ + + void GuiRibbonTab::BeforeControlTemplateUninstalled_() + { + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + if (auto bhc = ct->GetBeforeHeadersContainer()) + { + bhc->RemoveChild(beforeHeaders); + } + if (auto ahc = ct->GetAfterHeadersContainer()) + { + ahc->RemoveChild(afterHeaders); + } + } + + void GuiRibbonTab::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + if (auto bhc = ct->GetBeforeHeadersContainer()) + { + bhc->AddChild(beforeHeaders); + } + if (auto ahc = ct->GetAfterHeadersContainer()) + { + ahc->AddChild(afterHeaders); + } + } + + GuiRibbonTab::GuiRibbonTab(theme::ThemeName themeName) + :GuiTab(themeName) + { + beforeHeaders = new GuiBoundsComposition(); + beforeHeaders->SetAlignmentToParent(Margin(0, 0, 0, 0)); + beforeHeaders->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + afterHeaders = new GuiBoundsComposition(); + afterHeaders->SetAlignmentToParent(Margin(0, 0, 0, 0)); + afterHeaders->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + } + + GuiRibbonTab::~GuiRibbonTab() + { + if (!beforeHeaders->GetParent()) + { + SafeDeleteComposition(beforeHeaders); + } + if (!afterHeaders->GetParent()) + { + SafeDeleteComposition(afterHeaders); + } + } + + compositions::GuiGraphicsComposition* GuiRibbonTab::GetBeforeHeaders() + { + return beforeHeaders; + } + + compositions::GuiGraphicsComposition* GuiRibbonTab::GetAfterHeaders() + { + return afterHeaders; + } + +/*********************************************************************** +GuiRibbonGroupCollection +***********************************************************************/ + + bool GuiRibbonGroupCollection::QueryInsert(vint index, GuiRibbonGroup* const& value) + { + return !value->GetBoundsComposition()->GetParent(); + } + + void GuiRibbonGroupCollection::AfterInsert(vint index, GuiRibbonGroup* const& value) + { + value->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + + auto item = new GuiStackItemComposition(); + item->AddChild(value->GetBoundsComposition()); + + tabPage->stack->InsertStackItem(index, item); + } + + void GuiRibbonGroupCollection::AfterRemove(vint index, vint count) + { + for (vint i = 0; i < count; i++) + { + auto item = tabPage->stack->GetStackItems()[index]; + tabPage->stack->RemoveChild(item); + + item->RemoveChild(item->Children()[0]); + delete item; + } + } + + GuiRibbonGroupCollection::GuiRibbonGroupCollection(GuiRibbonTabPage* _tabPage) + :tabPage(_tabPage) + { + } + + GuiRibbonGroupCollection::~GuiRibbonGroupCollection() + { + } + +/*********************************************************************** +GuiRibbonTabPage +***********************************************************************/ + + GuiRibbonTabPage::GuiRibbonTabPage(theme::ThemeName themeName) + :GuiTabPage(themeName) + , groups(this) + { + stack = new GuiStackComposition(); + stack->SetDirection(GuiStackComposition::Horizontal); + stack->SetAlignmentToParent(Margin(2, 2, 2, 2)); + stack->SetPadding(2); + stack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + responsiveStack = new GuiResponsiveStackComposition(); + responsiveStack->SetDirection(ResponsiveDirection::Horizontal); + responsiveStack->SetAlignmentToParent(Margin(0, 0, 0, 0)); + responsiveStack->AddChild(stack); + + responsiveContainer = new GuiResponsiveContainerComposition(); + responsiveContainer->SetAlignmentToParent(Margin(0, 0, 0, 0)); + responsiveContainer->SetResponsiveTarget(responsiveStack); + + containerComposition->AddChild(responsiveContainer); + + HighlightedChanged.SetAssociatedComposition(boundsComposition); + } + + GuiRibbonTabPage::~GuiRibbonTabPage() + { + } + + bool GuiRibbonTabPage::GetHighlighted() + { + return highlighted; + } + + void GuiRibbonTabPage::SetHighlighted(bool value) + { + if (highlighted != value) + { + highlighted = value; + HighlightedChanged.Execute(GetNotifyEventArguments()); + } + } + + collections::ObservableListBase& GuiRibbonTabPage::GetGroups() + { + return groups; + } + +/*********************************************************************** +GuiRibbonGroupItemCollection +***********************************************************************/ + + bool GuiRibbonGroupItemCollection::QueryInsert(vint index, GuiControl* const& value) + { + return !value->GetBoundsComposition()->GetParent(); + } + + void GuiRibbonGroupItemCollection::AfterInsert(vint index, GuiControl* const& value) + { + value->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + + auto item = new GuiStackItemComposition(); + item->AddChild(value->GetBoundsComposition()); + + group->stack->InsertStackItem(index, item); + } + + void GuiRibbonGroupItemCollection::AfterRemove(vint index, vint count) + { + for (vint i = 0; i < count; i++) + { + auto item = group->stack->GetStackItems()[index]; + group->stack->RemoveChild(item); + + item->RemoveChild(item->Children()[0]); + delete item; + } + } + + GuiRibbonGroupItemCollection::GuiRibbonGroupItemCollection(GuiRibbonGroup* _group) + :group(_group) + { + } + + GuiRibbonGroupItemCollection::~GuiRibbonGroupItemCollection() + { + } + +/*********************************************************************** +GuiRibbonGroup::CommandExecutor +***********************************************************************/ + + GuiRibbonGroup::CommandExecutor::CommandExecutor(GuiRibbonGroup* _group) + :group(_group) + { + } + + GuiRibbonGroup::CommandExecutor::~CommandExecutor() + { + } + + void GuiRibbonGroup::CommandExecutor::NotifyExpandButtonClicked() + { + group->ExpandButtonClicked.Execute(group->GetNotifyEventArguments()); + } + +/*********************************************************************** +GuiRibbonGroupMenu +***********************************************************************/ + + class GuiRibbonGroupMenu : public GuiMenu, public Description + { + private: + IGuiMenuService::Direction GetPreferredDirection()override + { + return IGuiMenuService::Horizontal; + } + + bool IsActiveState()override + { + return false; + } + + public: + GuiRibbonGroupMenu(theme::ThemeName themeName, GuiControl* _owner) + :GuiMenu(themeName, _owner) + { + } + }; + +/*********************************************************************** +GuiRibbonGroup +***********************************************************************/ + + void GuiRibbonGroup::BeforeControlTemplateUninstalled_() + { + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + ct->SetCommands(nullptr); + } + + void GuiRibbonGroup::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + ct->SetExpandable(expandable); + ct->SetCollapsed(responsiveView->GetCurrentView() == responsiveFixedButton); + ct->SetCommands(commandExecutor.Obj()); + dropdownButton->SetControlTemplate(ct->GetLargeDropdownButtonTemplate()); + dropdownMenu->SetControlTemplate(ct->GetSubMenuTemplate()); + } + + void GuiRibbonGroup::OnBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + dropdownMenu->GetBoundsComposition()->SetPreferredMinSize(Size(0, containerComposition->GetBounds().Height())); + } + + void GuiRibbonGroup::OnTextChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + dropdownButton->SetText(GetText()); + } + + void GuiRibbonGroup::OnBeforeSubMenuOpening(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (responsiveView->GetViews().Contains(responsiveFixedButton)) + { + auto currentDropdown = dropdownMenu; + if (items.Count() == 1) + { + if (auto provider = items[0]->QueryTypedService()) + { + if (auto menu = provider->ProvideDropdownMenu()) + { + currentDropdown = menu; + } + } + } + dropdownButton->SetSubMenu(currentDropdown, false); + } + } + + void GuiRibbonGroup::OnBeforeSwitchingView(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) + { + if (auto ct = GetControlTemplateObject(false)) + { + ct->SetCollapsed(arguments.itemIndex == 1); + } + + if (arguments.itemIndex == 0) + { + while (responsiveStack->LevelDown()); + dropdownMenu->GetContainerComposition()->RemoveChild(stack); + responsiveStack->AddChild(stack); + dropdownButton->SetSubMenu(nullptr, false); + } + else + { + while (responsiveStack->LevelUp()); + responsiveStack->RemoveChild(stack); + dropdownMenu->GetContainerComposition()->AddChild(stack); + } + } + + GuiRibbonGroup::GuiRibbonGroup(theme::ThemeName themeName) + :GuiControl(themeName) + , items(this) + { + commandExecutor = new CommandExecutor(this); + { + stack = new GuiStackComposition(); + stack->SetDirection(GuiStackComposition::Horizontal); + stack->SetAlignmentToParent(Margin(0, 0, 0, 0)); + stack->SetPadding(2); + stack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + responsiveStack = new GuiResponsiveStackComposition(); + responsiveStack->SetDirection(ResponsiveDirection::Horizontal); + responsiveStack->SetAlignmentToParent(Margin(0, 0, 0, 0)); + responsiveStack->AddChild(stack); + } + { + dropdownButton = new GuiToolstripButton(theme::ThemeName::RibbonLargeDropdownButton); + dropdownButton->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + + responsiveFixedButton = new GuiResponsiveFixedComposition(); + responsiveFixedButton->SetAlignmentToParent(Margin(0, 0, 0, 0)); + responsiveFixedButton->AddChild(dropdownButton->GetBoundsComposition()); + + dropdownMenu = new GuiRibbonGroupMenu(theme::ThemeName::Menu, dropdownButton); + } + + responsiveView = new GuiResponsiveViewComposition(); + responsiveView->SetAlignmentToParent(Margin(0, 0, 0, 0)); + responsiveView->GetViews().Add(responsiveStack); + + containerComposition->AddChild(responsiveView); + + ExpandableChanged.SetAssociatedComposition(boundsComposition); + ExpandButtonClicked.SetAssociatedComposition(boundsComposition); + LargeImageChanged.SetAssociatedComposition(boundsComposition); + + TextChanged.AttachMethod(this, &GuiRibbonGroup::OnTextChanged); + boundsComposition->BoundsChanged.AttachMethod(this, &GuiRibbonGroup::OnBoundsChanged); + responsiveView->BeforeSwitchingView.AttachMethod(this, &GuiRibbonGroup::OnBeforeSwitchingView); + dropdownButton->BeforeSubMenuOpening.AttachMethod(this, &GuiRibbonGroup::OnBeforeSubMenuOpening); + } + + GuiRibbonGroup::~GuiRibbonGroup() + { + if (!responsiveView->GetViews().Contains(responsiveFixedButton)) + { + SafeDeleteComposition(responsiveFixedButton); + } + delete dropdownMenu; + } + + bool GuiRibbonGroup::GetExpandable() + { + return expandable; + } + + void GuiRibbonGroup::SetExpandable(bool value) + { + if (expandable != value) + { + expandable = value; + GetControlTemplateObject(true)->SetExpandable(expandable); + ExpandableChanged.Execute(GetNotifyEventArguments()); + } + } + + Ptr GuiRibbonGroup::GetLargeImage() + { + return largeImage; + } + + void GuiRibbonGroup::SetLargeImage(Ptr value) + { + if (largeImage != value) + { + largeImage = value; + dropdownButton->SetLargeImage(value); + LargeImageChanged.Execute(GetNotifyEventArguments()); + + if (value) + { + if (!responsiveView->GetViews().Contains(responsiveFixedButton)) + { + responsiveView->GetViews().Add(responsiveFixedButton); + } + } + else + { + if (responsiveView->GetViews().Contains(responsiveFixedButton)) + { + responsiveView->GetViews().Remove(responsiveFixedButton); + } + } + } + } + + collections::ObservableListBase& GuiRibbonGroup::GetItems() + { + return items; + } + +/*********************************************************************** +GuiRibbonIconLabel +***********************************************************************/ + + void GuiRibbonIconLabel::BeforeControlTemplateUninstalled_() + { + } + + void GuiRibbonIconLabel::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + ct->SetImage(image); + } + + GuiRibbonIconLabel::GuiRibbonIconLabel(theme::ThemeName themeName) + :GuiControl(themeName) + { + ImageChanged.SetAssociatedComposition(boundsComposition); + } + + GuiRibbonIconLabel::~GuiRibbonIconLabel() + { + } + + Ptr GuiRibbonIconLabel::GetImage() + { + return image; + } + + void GuiRibbonIconLabel::SetImage(Ptr value) + { + if (image != value) + { + image = value; + GetControlTemplateObject(true)->SetImage(image); + ImageChanged.Execute(GetNotifyEventArguments()); + } + } + +/*********************************************************************** +GuiRibbonButtonsItemCollection +***********************************************************************/ + + bool GuiRibbonButtonsItemCollection::QueryInsert(vint index, GuiControl* const& value) + { + return !value->GetBoundsComposition()->GetParent(); + } + + void GuiRibbonButtonsItemCollection::AfterInsert(vint index, GuiControl* const& value) + { + buttons->responsiveView->GetSharedControls().Add(value); + buttons->SetButtonThemeName(buttons->responsiveView->GetCurrentView(), value); + + for (vint i = 0; i < sizeof(buttons->views) / sizeof(*buttons->views); i++) + { + if (auto view = buttons->views[i]) + { + auto stack = dynamic_cast(view->Children()[0]); + + auto shared = new GuiResponsiveSharedComposition(); + shared->SetAlignmentToParent(Margin(0, 0, 0, 0)); + shared->SetShared(value); + + auto item = new GuiStackItemComposition(); + item->AddChild(shared); + + stack->InsertStackItem(index, item); + } + } + } + + void GuiRibbonButtonsItemCollection::BeforeRemove(vint index, GuiControl* const& value) + { + CHECK_FAIL(L"GuiRibbonButtonsItemCollection::BeforeRemove(vint, GuiControl* const&)#Controls are not allowed to be removed from GuiRibbonButtons."); + } + + GuiRibbonButtonsItemCollection::GuiRibbonButtonsItemCollection(GuiRibbonButtons* _buttons) + :buttons(_buttons) + { + } + + GuiRibbonButtonsItemCollection::~GuiRibbonButtonsItemCollection() + { + } + +/*********************************************************************** +GuiRibbonButtons +***********************************************************************/ + + void GuiRibbonButtons::BeforeControlTemplateUninstalled_() + { + } + + void GuiRibbonButtons::AfterControlTemplateInstalled_(bool initialize) + { + FOREACH(GuiControl*, button, buttons) + { + SetButtonThemeName(responsiveView->GetCurrentView(), button); + } + } + + void GuiRibbonButtons::OnBeforeSwitchingView(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) + { + FOREACH(GuiControl*, button, buttons) + { + SetButtonThemeName(responsiveView->GetViews()[arguments.itemIndex], button); + } + } + + void GuiRibbonButtons::SetButtonThemeName(compositions::GuiResponsiveCompositionBase* fixed, GuiControl* button) + { + if (fixed && button) + { + auto themeName = button->GetControlThemeName(); + vint type = -1; + switch (themeName) + { + case ThemeName::RibbonLargeButton: + case ThemeName::RibbonSmallButton: + case ThemeName::ToolstripButton: + type = 0; + break; + case ThemeName::RibbonLargeDropdownButton: + case ThemeName::RibbonSmallDropdownButton: + case ThemeName::ToolstripDropdownButton: + type = 1; + break; + case ThemeName::RibbonLargeSplitButton: + case ThemeName::RibbonSmallSplitButton: + case ThemeName::ToolstripSplitButton: + type = 2; + break; + case ThemeName::RibbonSmallIconLabel: + case ThemeName::RibbonIconLabel: + type = 3; + break; + default:; + } + + if (type != -1) + { + ThemeName themeName = ThemeName::Unknown; + TemplateProperty controlTemplate; + + if (fixed == views[(vint)RibbonButtonSize::Large]) + { + switch (type) + { + case 0: themeName = ThemeName::RibbonLargeButton; break; + case 1: themeName = ThemeName::RibbonLargeDropdownButton; break; + case 2: themeName = ThemeName::RibbonLargeSplitButton; break; + case 3: themeName = ThemeName::RibbonSmallIconLabel; break; + } + } + else if (fixed == views[(vint)RibbonButtonSize::Small]) + { + switch (type) + { + case 0: themeName = ThemeName::RibbonSmallButton; break; + case 1: themeName = ThemeName::RibbonSmallDropdownButton; break; + case 2: themeName = ThemeName::RibbonSmallSplitButton; break; + case 3: themeName = ThemeName::RibbonSmallIconLabel; break; + } + } + else if (fixed == views[(vint)RibbonButtonSize::Icon]) + { + switch (type) + { + case 0: themeName = ThemeName::ToolstripButton; break; + case 1: themeName = ThemeName::ToolstripDropdownButton; break; + case 2: themeName = ThemeName::ToolstripSplitButton; break; + case 3: themeName = ThemeName::RibbonIconLabel; break; + } + } + + if (auto ct = GetControlTemplateObject(false)) + { + if (fixed == views[(vint)RibbonButtonSize::Large]) + { + switch (type) + { + case 0: controlTemplate = ct->GetLargeButtonTemplate(); break; + case 1: controlTemplate = ct->GetLargeDropdownButtonTemplate(); break; + case 2: controlTemplate = ct->GetLargeSplitButtonTemplate(); break; + case 3: controlTemplate = ct->GetSmallIconLabelTemplate(); break; + } + } + else if (fixed == views[(vint)RibbonButtonSize::Small]) + { + switch (type) + { + case 0: controlTemplate = ct->GetSmallButtonTemplate(); break; + case 1: controlTemplate = ct->GetSmallDropdownButtonTemplate(); break; + case 2: controlTemplate = ct->GetSmallSplitButtonTemplate(); break; + case 3: controlTemplate = ct->GetSmallIconLabelTemplate(); break; + } + } + else if (fixed == views[(vint)RibbonButtonSize::Icon]) + { + switch (type) + { + case 0: controlTemplate = ct->GetIconButtonTemplate(); break; + case 1: controlTemplate = ct->GetIconDropdownButtonTemplate(); break; + case 2: controlTemplate = ct->GetIconSplitButtonTemplate(); break; + case 3: controlTemplate = ct->GetIconLabelTemplate(); break; + } + } + } + + button->SetControlThemeNameAndTemplate(themeName, controlTemplate); + } + } + } + + GuiRibbonButtons::GuiRibbonButtons(theme::ThemeName themeName, RibbonButtonSize _maxSize, RibbonButtonSize _minSize) + :GuiControl(themeName) + , maxSize(_maxSize) + , minSize(_minSize) + , buttons(this) + { + responsiveView = new GuiResponsiveViewComposition(); + responsiveView->SetDirection(ResponsiveDirection::Horizontal); + responsiveView->SetAlignmentToParent(Margin(0, 0, 0, 0)); + responsiveView->BeforeSwitchingView.AttachMethod(this, &GuiRibbonButtons::OnBeforeSwitchingView); + + auto installButton = [&](GuiTableComposition* table, vint row, vint column, GuiControl* buttonContainer) + { + auto shared = new GuiResponsiveSharedComposition(); + shared->SetAlignmentToParent(Margin(0, 0, 0, 0)); + shared->SetShared(buttonContainer); + + auto cell = new GuiCellComposition(); + cell->SetSite(row, column, 1, 1); + cell->AddChild(shared); + + table->AddChild(cell); + }; + + for (vint i = 0; i < sizeof(views) / sizeof(*views); i++) + { + if ((vint)maxSize <= i && i <= (vint)minSize) + { + auto stack = new GuiStackComposition(); + stack->SetAlignmentToParent(Margin(0, 0, 0, 0)); + stack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + stack->SetDirection(i == 0 ? GuiStackComposition::Horizontal : GuiStackComposition::Vertical); + + views[i] = new GuiResponsiveFixedComposition(); + views[i]->AddChild(stack); + responsiveView->GetViews().Add(views[i]); + } + } + + auto sharedSizeRootComposition = new GuiSharedSizeRootComposition(); + sharedSizeRootComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + sharedSizeRootComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + sharedSizeRootComposition->AddChild(responsiveView); + + containerComposition->AddChild(sharedSizeRootComposition); + } + + GuiRibbonButtons::~GuiRibbonButtons() + { + } + + collections::ObservableListBase& GuiRibbonButtons::GetButtons() + { + return buttons; + } + +/*********************************************************************** +GuiRibbonToolstripsGroupCollection +***********************************************************************/ + + bool GuiRibbonToolstripsGroupCollection::QueryInsert(vint index, GuiToolstripGroup* const& value) + { + return !value->GetBoundsComposition()->GetParent(); + } + + void GuiRibbonToolstripsGroupCollection::AfterInsert(vint index, GuiToolstripGroup* const& value) + { + toolstrips->RearrangeToolstripGroups(); + } + + void GuiRibbonToolstripsGroupCollection::AfterRemove(vint index, vint count) + { + toolstrips->RearrangeToolstripGroups(); + } + + GuiRibbonToolstripsGroupCollection::GuiRibbonToolstripsGroupCollection(GuiRibbonToolstrips* _toolstrips) + :toolstrips(_toolstrips) + { + } + + GuiRibbonToolstripsGroupCollection::~GuiRibbonToolstripsGroupCollection() + { + } + +/*********************************************************************** +GuiRibbonToolstrips +***********************************************************************/ + +#define ARRLEN(X) sizeof(X) / sizeof(*X) + + void GuiRibbonToolstrips::BeforeControlTemplateUninstalled_() + { + } + + void GuiRibbonToolstrips::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + for (vint i = 0; i < ARRLEN(toolbars); i++) + { + toolbars[i]->SetControlTemplate(ct->GetToolbarTemplate()); + } + } + + void GuiRibbonToolstrips::OnBeforeSwitchingView(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) + { + RearrangeToolstripGroups(arguments.itemIndex); + } + + void GuiRibbonToolstrips::RearrangeToolstripGroups(vint viewIndex) + { + static_assert(ARRLEN(longContainers) == 2, ""); + static_assert(ARRLEN(shortContainers) == 3, ""); + + if (viewIndex == -1) + { + viewIndex = responsiveView->GetViews().IndexOf(responsiveView->GetCurrentView()); + } + + for (vint i = 0; i < ARRLEN(longContainers); i++) + { + longContainers[i]->GetToolstripItems().Clear(); + } + for (vint i = 0; i < ARRLEN(shortContainers); i++) + { + shortContainers[i]->GetToolstripItems().Clear(); + } + + vint count = viewIndex == 0 ? 2 : 3; + + if (groups.Count() <= count) + { + auto containers = viewIndex == 0 ? longContainers : shortContainers; + for (vint i = 0; i < groups.Count(); i++) + { + containers[i]->GetToolstripItems().Add(groups[i]); + } + } + else if (count == 3) + { +#define DELTA(POSTFIX) (abs(count1##POSTFIX - count2##POSTFIX) + abs(count2##POSTFIX - count3##POSTFIX) + abs(count3##POSTFIX - count1##POSTFIX)) +#define DEFINE_COUNT(POSTFIX, OFFSET_FIRST, OFFSET_LAST) \ + vint count1##POSTFIX = count1_o + (OFFSET_FIRST); \ + vint count2##POSTFIX = count2_o - (OFFSET_FIRST) - (OFFSET_LAST); \ + vint count3##POSTFIX = count3_o + (OFFSET_LAST) +#define MIN(a, b) (a)<(b)?(a):(b) + + vint firstGroupCount = 0; + vint lastGroupCount = 0; + + vint count1_o = 0; + vint count2_o = From(groups) + .Select([](GuiToolstripGroup* group) {return group->GetToolstripItems().Count(); }) + .Aggregate([](vint a, vint b) {return a + b; }); + vint count3_o = 0; + vint delta_o = DELTA(_o); + + while (firstGroupCount + lastGroupCount < groups.Count()) + { + auto newFirstGroup = groups[firstGroupCount]; + auto newLastGroup = groups[groups.Count() - lastGroupCount - 1]; + + DEFINE_COUNT(_f, newFirstGroup->GetToolstripItems().Count(), 0); + vint delta_f = DELTA(_f); + + DEFINE_COUNT(_l, 0, newLastGroup->GetToolstripItems().Count()); + vint delta_l = DELTA(_l); + + vint delta = MIN(delta_o, MIN(delta_f, delta_l)); + if (delta == delta_f) + { + firstGroupCount++; + count1_o = count1_f; + count2_o = count2_f; + count3_o = count3_f; + delta_o = delta_f; + } + else if (delta == delta_l) + { + lastGroupCount++; + count1_o = count1_l; + count2_o = count2_l; + count3_o = count3_l; + delta_o = delta_l; + } + else + { + break; + } + } + + vint minMiddle = firstGroupCount; + vint maxMiddle = groups.Count() - lastGroupCount - 1; + for (vint j = 0; j < groups.Count(); j++) + { + shortContainers[ + j < minMiddle ? 0 : + j>maxMiddle ? 2 : + 1 + ]->GetToolstripItems().Add(groups[j]); + } + +#undef MIN +#undef DEFINE_COUNT +#undef DELTA + } + else if (count == 2) + { + vint firstGroupCount = groups.Count(); + { + vint count1 = 0; + vint count2 = From(groups) + .Select([](GuiToolstripGroup* group) {return group->GetToolstripItems().Count(); }) + .Aggregate([](vint a, vint b) {return a + b; }); + vint delta = abs(count2 - count1); + + for (vint i = 0; i < groups.Count(); i++) + { + auto groupCount = groups[i]->GetToolstripItems().Count(); + vint count1_2 = count1 + groupCount; + vint count2_2 = count2 - groupCount; + vint delta_2 = abs(count2_2 - count1_2); + + if (delta < delta_2) + { + firstGroupCount = i; + break; + } + + count1 = count1_2; + count2 = count2_2; + delta = delta_2; + } + } + + for (vint j = 0; j < groups.Count(); j++) + { + longContainers[j < firstGroupCount ? 0 : 1]->GetToolstripItems().Add(groups[j]); + } + } + } + + GuiRibbonToolstrips::GuiRibbonToolstrips(theme::ThemeName themeName) + :GuiControl(themeName) + , groups(this) + { + responsiveView = new GuiResponsiveViewComposition(); + responsiveView->SetDirection(ResponsiveDirection::Horizontal); + responsiveView->SetAlignmentToParent(Margin(0, 0, 0, 0)); + responsiveView->BeforeSwitchingView.AttachMethod(this, &GuiRibbonToolstrips::OnBeforeSwitchingView); + + vint toolbarIndex = 0; + for (vint i = 0; i < sizeof(views) / sizeof(*views); i++) + { + auto containers = i == 0 ? longContainers : shortContainers; + vint count = i == 0 ? 2 : 3; + + auto table = new GuiTableComposition(); + table->SetAlignmentToParent(Margin(0, 0, 0, 0)); + table->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + table->SetRowsAndColumns(count * 2 + 1, 1); + table->SetColumnOption(0, GuiCellOption::MinSizeOption()); + table->SetRowOption(0, GuiCellOption::PercentageOption(1.0)); + for (vint j = 0; j < count; j++) + { + table->SetRowOption(j * 2 + 1, GuiCellOption::MinSizeOption()); + table->SetRowOption(j * 2 + 2, GuiCellOption::PercentageOption(1.0)); + } + + for (vint j = 0; j < count; j++) + { + auto toolbar = new GuiToolstripToolBar(theme::ThemeName::ToolstripToolBar); + toolbar->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + toolbars[toolbarIndex++] = toolbar; + + auto cell = new GuiCellComposition(); + cell->SetSite(j * 2 + 1, 0, 1, 1); + cell->AddChild(toolbar->GetBoundsComposition()); + table->AddChild(cell); + + auto container = new GuiToolstripGroupContainer(theme::ThemeName::CustomControl); + toolbar->GetToolstripItems().Add(container); + containers[j] = container; + } + + views[i] = new GuiResponsiveFixedComposition(); + views[i]->AddChild(table); + responsiveView->GetViews().Add(views[i]); + } + + containerComposition->AddChild(responsiveView); + } + + GuiRibbonToolstrips::~GuiRibbonToolstrips() + { + } + + collections::ObservableListBase& GuiRibbonToolstrips::GetGroups() + { + return groups; + } + +#undef ARRLEN + +/*********************************************************************** +GuiRibbonGallery::CommandExecutor +***********************************************************************/ + + GuiRibbonGallery::CommandExecutor::CommandExecutor(GuiRibbonGallery* _gallery) + :gallery(_gallery) + { + } + + GuiRibbonGallery::CommandExecutor::~CommandExecutor() + { + } + + void GuiRibbonGallery::CommandExecutor::NotifyScrollUp() + { + gallery->RequestedScrollUp.Execute(gallery->GetNotifyEventArguments()); + } + + void GuiRibbonGallery::CommandExecutor::NotifyScrollDown() + { + gallery->RequestedScrollDown.Execute(gallery->GetNotifyEventArguments()); + } + + void GuiRibbonGallery::CommandExecutor::NotifyDropdown() + { + gallery->RequestedDropdown.Execute(gallery->GetNotifyEventArguments()); + } + +/*********************************************************************** +GuiRibbonGallery +***********************************************************************/ + + void GuiRibbonGallery::BeforeControlTemplateUninstalled_() + { + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + ct->SetCommands(nullptr); + } + + void GuiRibbonGallery::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + ct->SetCommands(commandExecutor.Obj()); + ct->SetScrollUpEnabled(scrollUpEnabled); + ct->SetScrollDownEnabled(scrollDownEnabled); + } + + GuiRibbonGallery::GuiRibbonGallery(theme::ThemeName themeName) + :GuiControl(themeName) + { + commandExecutor = new CommandExecutor(this); + + ScrollUpEnabledChanged.SetAssociatedComposition(boundsComposition); + ScrollDownEnabledChanged.SetAssociatedComposition(boundsComposition); + RequestedScrollUp.SetAssociatedComposition(boundsComposition); + RequestedScrollDown.SetAssociatedComposition(boundsComposition); + RequestedDropdown.SetAssociatedComposition(boundsComposition); + } + + GuiRibbonGallery::~GuiRibbonGallery() + { + } + + bool GuiRibbonGallery::GetScrollUpEnabled() + { + return scrollUpEnabled; + } + + void GuiRibbonGallery::SetScrollUpEnabled(bool value) + { + if (scrollUpEnabled != value) + { + scrollUpEnabled = value; + GetControlTemplateObject(true)->SetScrollUpEnabled(value); + } + } + + bool GuiRibbonGallery::GetScrollDownEnabled() + { + return scrollDownEnabled; + } + + void GuiRibbonGallery::SetScrollDownEnabled(bool value) + { + if (scrollDownEnabled != value) + { + scrollDownEnabled = value; + GetControlTemplateObject(true)->SetScrollDownEnabled(value); + } + } + +/*********************************************************************** +GuiRibbonToolstripMenu +***********************************************************************/ + + void GuiRibbonToolstripMenu::BeforeControlTemplateUninstalled_() + { + auto ct = GetControlTemplateObject(false); + if (!ct) return; + + if (auto cc = ct->GetContentComposition()) + { + cc->RemoveChild(contentComposition); + } + } + + void GuiRibbonToolstripMenu::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + if (auto cc = ct->GetContentComposition()) + { + cc->AddChild(contentComposition); + } + } + + GuiRibbonToolstripMenu::GuiRibbonToolstripMenu(theme::ThemeName themeName, GuiControl* owner) + :GuiToolstripMenu(themeName, owner) + { + contentComposition = new GuiBoundsComposition(); + contentComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + contentComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + } + + GuiRibbonToolstripMenu::~GuiRibbonToolstripMenu() + { + if (!contentComposition->GetParent()) + { + SafeDeleteComposition(contentComposition); + } + } + + compositions::GuiGraphicsComposition* GuiRibbonToolstripMenu::GetContentComposition() + { + return contentComposition; + } + } + } +} + + +/*********************************************************************** +.\CONTROLS\TOOLSTRIPPACKAGE\GUIRIBBONGALLERYLIST.CPP +***********************************************************************/ + + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace reflection::description; + using namespace collections; + using namespace compositions; + using namespace templates; + + namespace list + { + +/*********************************************************************** +list::GalleryGroup +***********************************************************************/ + + GalleryGroup::GalleryGroup() + { + } + + GalleryGroup::~GalleryGroup() + { + if (eventHandler) + { + itemValues.Cast()->ItemChanged.Remove(eventHandler); + } + } + + WString GalleryGroup::GetName() + { + return name; + } + + Ptr GalleryGroup::GetItemValues() + { + return itemValues; + } + +/*********************************************************************** +list::GroupedDataSource +***********************************************************************/ + + void GroupedDataSource::RebuildItemSource() + { + ignoreGroupChanged = true; + joinedItemSource.Clear(); + groupedItemSource.Clear(); + cachedGroupItemCounts.Clear(); + ignoreGroupChanged = false; + + if (itemSource) + { + if (GetGroupEnabled()) + { + FOREACH_INDEXER(Value, groupValue, index, GetLazyList(itemSource)) + { + auto group = MakePtr(); + group->name = titleProperty(groupValue); + group->itemValues = GetChildren(childrenProperty(groupValue)); + AttachGroupChanged(group, index); + groupedItemSource.Add(group); + } + } + else + { + auto group = MakePtr(); + group->itemValues = GetChildren(itemSource); + AttachGroupChanged(group, 0); + groupedItemSource.Add(group); + } + } + } + + Ptr GroupedDataSource::GetChildren(Ptr children) + { + if (!children) + { + return nullptr; + } + else if (auto list = children.Cast()) + { + return list; + } + else + { + return IValueList::Create(GetLazyList(children)); + } + } + + void GroupedDataSource::AttachGroupChanged(Ptr group, vint index) + { + if (auto observable = group->itemValues.Cast()) + { + group->eventHandler = observable->ItemChanged.Add([this, index](vint start, vint oldCount, vint newCount) + { + OnGroupItemChanged(index, start, oldCount, newCount); + }); + } + } + + void GroupedDataSource::OnGroupChanged(vint start, vint oldCount, vint newCount) + { + if (!ignoreGroupChanged) + { + for (vint i = 0; i < oldCount; i++) + { + RemoveGroupFromJoined(start); + } + for (vint i = 0; i < newCount; i++) + { + InsertGroupToJoined(start + i); + } + } + } + + void GroupedDataSource::OnGroupItemChanged(vint index, vint start, vint oldCount, vint newCount) + { + if (!ignoreGroupChanged) + { + vint countBeforeGroup = GetCountBeforeGroup(index); + vint joinedIndex = countBeforeGroup + start; + vint minCount = oldCount < newCount ? oldCount : newCount; + auto itemValues = groupedItemSource[index]->itemValues; + + for (vint i = 0; i < minCount; i++) + { + joinedItemSource.Set(joinedIndex + i, itemValues->Get(start + i)); + } + + if (oldCount < newCount) + { + for (vint i = minCount; i < newCount; i++) + { + joinedItemSource.Insert(joinedIndex + i, itemValues->Get(start + i)); + } + } + else if (oldCount > newCount) + { + for (vint i = minCount; i < oldCount; i++) + { + joinedItemSource.RemoveAt(joinedIndex + i); + } + } + + cachedGroupItemCounts[index] += newCount - oldCount; + } + } + + vint GroupedDataSource::GetCountBeforeGroup(vint index) + { + vint count = 0; + for (vint i = 0; i < index; i++) + { + count += cachedGroupItemCounts[i]; + } + return count; + } + + void GroupedDataSource::InsertGroupToJoined(vint index) + { + vint countBeforeGroup = GetCountBeforeGroup(index); + auto group = groupedItemSource[index]; + vint itemCount = group->itemValues ? group->itemValues->GetCount() : 0; + cachedGroupItemCounts.Insert(index, itemCount); + + if (itemCount > 0) + { + for (vint i = 0; i < itemCount; i++) + { + joinedItemSource.Insert(countBeforeGroup + i, group->itemValues->Get(i)); + } + } + } + + void GroupedDataSource::RemoveGroupFromJoined(vint index) + { + vint countBeforeGroup = GetCountBeforeGroup(index); + joinedItemSource.RemoveRange(countBeforeGroup, cachedGroupItemCounts[index]); + cachedGroupItemCounts.RemoveAt(index); + } + + GroupedDataSource::GroupedDataSource(compositions::GuiGraphicsComposition* _associatedComposition) + :associatedComposition(_associatedComposition) + { + GroupEnabledChanged.SetAssociatedComposition(associatedComposition); + GroupTitlePropertyChanged.SetAssociatedComposition(associatedComposition); + GroupChildrenPropertyChanged.SetAssociatedComposition(associatedComposition); + + groupChangedHandler = groupedItemSource.GetWrapper()->ItemChanged.Add(this, &GroupedDataSource::OnGroupChanged); + } + + GroupedDataSource::~GroupedDataSource() + { + joinedItemSource.GetWrapper()->ItemChanged.Remove(groupChangedHandler); + } + + Ptr GroupedDataSource::GetItemSource() + { + return itemSource; + } + + void GroupedDataSource::SetItemSource(Ptr value) + { + if (itemSource != value) + { + itemSource = value; + RebuildItemSource(); + } + } + + bool GroupedDataSource::GetGroupEnabled() + { + return titleProperty && childrenProperty; + } + + ItemProperty GroupedDataSource::GetGroupTitleProperty() + { + return titleProperty; + } + + void GroupedDataSource::SetGroupTitleProperty(const ItemProperty& value) + { + if (titleProperty != value) + { + titleProperty = value; + GroupTitlePropertyChanged.Execute(GuiEventArgs(associatedComposition)); + GroupEnabledChanged.Execute(GuiEventArgs(associatedComposition)); + RebuildItemSource(); + } + } + + ItemProperty> GroupedDataSource::GetGroupChildrenProperty() + { + return childrenProperty; + } + + void GroupedDataSource::SetGroupChildrenProperty(const ItemProperty>& value) + { + if (childrenProperty != value) + { + childrenProperty = value; + GroupChildrenPropertyChanged.Execute(GuiEventArgs(associatedComposition)); + GroupEnabledChanged.Execute(GuiEventArgs(associatedComposition)); + RebuildItemSource(); + } + } + + const GroupedDataSource::GalleryGroupList& GroupedDataSource::GetGroups() + { + return groupedItemSource; + } + } + +/*********************************************************************** +GuiBindableRibbonGalleryList +***********************************************************************/ + + void GuiBindableRibbonGalleryList::BeforeControlTemplateUninstalled_() + { + } + + void GuiBindableRibbonGalleryList::AfterControlTemplateInstalled_(bool initialize) + { + auto ct = GetControlTemplateObject(true); + itemList->SetControlTemplate(ct->GetItemListTemplate()); + subMenu->SetControlTemplate(ct->GetMenuTemplate()); + groupContainer->SetControlTemplate(ct->GetGroupContainerTemplate()); + MenuResetGroupTemplate(); + UpdateLayoutSizeOffset(); + } + + void GuiBindableRibbonGalleryList::UpdateLayoutSizeOffset() + { + auto cSize = itemList->GetContainerComposition()->GetBounds(); + auto bSize = itemList->GetBoundsComposition()->GetBounds(); + layout->SetSizeOffset(Size(bSize.Width() - cSize.Width(), bSize.Height() - cSize.Height())); + + if (layout->GetItemWidth() > 0) + { + vint columns = layout->GetVisibleItemCount(); + if (columns == 0) columns = 1; + vint rows = (visibleItemCount + columns - 1) / columns; + vint height = (vint)(layout->GetBounds().Height()*(rows + 0.5)); + groupContainer->GetBoundsComposition()->SetPreferredMinSize(Size(0, height)); + } + else + { + groupContainer->GetBoundsComposition()->SetPreferredMinSize(Size(0, 0)); + } + } + + void GuiBindableRibbonGalleryList::OnItemListSelectionChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + auto pos = IndexToGalleryPos(itemList->GetSelectedItemIndex()); + if (pos.group != -1 && pos.item != -1) + { + for (vint i = 0; i < groupedItemSource.Count(); i++) + { + auto group = groupedItemSource[i]; + if (group->GetItemValues()) + { + vint count = group->GetItemValues()->GetCount(); + for (vint j = 0; j < count; j++) + { + auto background = MenuGetGroupItemBackground(i, j); + background->SetSelected(pos.group == i && pos.item == j); + } + } + } + } + + if (!skipItemAppliedEvent && itemList->GetSelectedItemIndex() != -1) + { + GuiItemEventArgs itemAppliedArgs(boundsComposition); + itemAppliedArgs.itemIndex = itemList->GetSelectedItemIndex(); + ItemApplied.Execute(itemAppliedArgs); + } + SelectionChanged.Execute(GetNotifyEventArguments()); + } + + void GuiBindableRibbonGalleryList::OnItemListItemMouseEnter(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) + { + StartPreview(arguments.itemIndex); + } + + void GuiBindableRibbonGalleryList::OnItemListItemMouseLeave(compositions::GuiGraphicsComposition* sender, compositions::GuiItemEventArgs& arguments) + { + StopPreview(arguments.itemIndex); + } + + void GuiBindableRibbonGalleryList::OnBoundsChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + UpdateLayoutSizeOffset(); + + auto bounds = boundsComposition->GetBounds(); + subMenu->GetBoundsComposition()->SetPreferredMinSize(Size(bounds.Width() + 20, 1)); + + for (vint i = 0; i < groupedItemSource.Count(); i++) + { + auto group = groupedItemSource[i]; + if (group->GetItemValues()) + { + vint count = group->GetItemValues()->GetCount(); + for (vint j = 0; j < count; j++) + { + auto background = MenuGetGroupItemBackground(i, j); + background->GetBoundsComposition()->SetPreferredMinSize(Size(0, bounds.Height())); + } + } + } + } + + void GuiBindableRibbonGalleryList::OnRequestedDropdown(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + subMenu->ShowPopup(this, Point(0, 0)); + } + + void GuiBindableRibbonGalleryList::OnRequestedScrollUp(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + itemListArranger->ScrollUp(); + } + + void GuiBindableRibbonGalleryList::OnRequestedScrollDown(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + itemListArranger->ScrollDown(); + } + + void GuiBindableRibbonGalleryList::MenuResetGroupTemplate() + { + groupStack->SetItemTemplate([this](const Value& groupValue)->GuiTemplate* + { + auto group = UnboxValue>(groupValue); + + auto groupTemplate = new GuiTemplate; + groupTemplate->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + auto groupContentStack = new GuiStackComposition; + groupContentStack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + groupContentStack->SetAlignmentToParent(Margin(0, 0, 0, 0)); + groupContentStack->SetDirection(GuiStackComposition::Vertical); + { + auto item = new GuiStackItemComposition; + groupContentStack->AddChild(item); + + auto header = new GuiControl(theme::ThemeName::RibbonToolstripHeader); + header->SetControlTemplate(GetControlTemplateObject(true)->GetHeaderTemplate()); + header->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + header->SetText(group->GetName()); + item->AddChild(header->GetBoundsComposition()); + } + if (itemStyle) + { + auto item = new GuiStackItemComposition; + item->SetPreferredMinSize(Size(1, 1)); + groupContentStack->AddChild(item); + + auto groupItemFlow = new GuiRepeatFlowComposition(); + groupItemFlow->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + groupItemFlow->SetAlignmentToParent(Margin(0, 0, 0, 0)); + groupItemFlow->SetItemSource(group->GetItemValues()); + groupItemFlow->SetItemTemplate([=](const Value& groupItemValue)->GuiTemplate* + { + auto groupItemTemplate = new GuiTemplate; + groupItemTemplate->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + + auto backgroundButton = new GuiSelectableButton(theme::ThemeName::ListItemBackground); + if (auto style = GetControlTemplateObject(true)->GetBackgroundTemplate()) + { + backgroundButton->SetControlTemplate(style); + } + backgroundButton->SetAutoSelection(false); + backgroundButton->Clicked.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) + { + auto groupIndex = groupStack->GetStackItems().IndexOf(dynamic_cast(groupTemplate->GetParent())); + auto itemIndex = groupItemFlow->GetFlowItems().IndexOf(dynamic_cast(groupItemTemplate->GetParent())); + auto index = GalleryPosToIndex({ groupIndex,itemIndex }); + itemList->SetSelected(index, true); + itemList->EnsureItemVisible(index); + subMenu->Close(); + }); + backgroundButton->GetBoundsComposition()->GetEventReceiver()->mouseEnter.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) + { + auto groupIndex = groupStack->GetStackItems().IndexOf(dynamic_cast(groupTemplate->GetParent())); + auto itemIndex = groupItemFlow->GetFlowItems().IndexOf(dynamic_cast(groupItemTemplate->GetParent())); + auto index = GalleryPosToIndex({ groupIndex,itemIndex }); + StartPreview(index); + }); + backgroundButton->GetBoundsComposition()->GetEventReceiver()->mouseLeave.AttachLambda([=](GuiGraphicsComposition* sender, GuiEventArgs& arguments) + { + auto groupIndex = groupStack->GetStackItems().IndexOf(dynamic_cast(groupTemplate->GetParent())); + auto itemIndex = groupItemFlow->GetFlowItems().IndexOf(dynamic_cast(groupItemTemplate->GetParent())); + auto index = GalleryPosToIndex({ groupIndex,itemIndex }); + StopPreview(index); + }); + groupItemTemplate->AddChild(backgroundButton->GetBoundsComposition()); + + auto itemTemplate = itemStyle(groupItemValue); + itemTemplate->SetAlignmentToParent(Margin(0, 0, 0, 0)); + backgroundButton->GetContainerComposition()->AddChild(itemTemplate); + + return groupItemTemplate; + }); + item->AddChild(groupItemFlow); + } + groupTemplate->AddChild(groupContentStack); + + return groupTemplate; + }); + } + + GuiControl* GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint groupIndex) + { + CHECK_ERROR(0 <= groupIndex && groupIndex < groupedItemSource.Count(), L"GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint)#Group index out of range"); + auto stackItem = groupStack->GetStackItems()[groupIndex]; + auto groupTemplate = stackItem->Children()[0]; + auto groupContentStack = dynamic_cast(groupTemplate->Children()[0]); + auto groupHeaderItem = groupContentStack->GetStackItems()[0]; + auto groupHeader = groupHeaderItem->Children()[0]->GetAssociatedControl(); + CHECK_ERROR(groupHeader, L"GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint)#Internal error."); + return groupHeader; + } + + compositions::GuiRepeatFlowComposition* GuiBindableRibbonGalleryList::MenuGetGroupFlow(vint groupIndex) + { + CHECK_ERROR(0 <= groupIndex && groupIndex < groupedItemSource.Count(), L"GuiBindableRibbonGalleryList::MenuGetGroupFlow(vint)#Group index out of range"); + if (!itemStyle) return nullptr; + auto stackItem = groupStack->GetStackItems()[groupIndex]; + auto groupTemplate = stackItem->Children()[0]; + auto groupContentStack = dynamic_cast(groupTemplate->Children()[0]); + auto groupContentItem = groupContentStack->GetStackItems()[1]; + auto groupFlow = dynamic_cast(groupContentItem->Children()[0]); + CHECK_ERROR(groupFlow, L"GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint)#Internal error."); + return groupFlow; + } + + GuiSelectableButton* GuiBindableRibbonGalleryList::MenuGetGroupItemBackground(vint groupIndex, vint itemIndex) + { + CHECK_ERROR(0 <= groupIndex && groupIndex < groupedItemSource.Count(), L"GuiBindableRibbonGalleryList::MenuGetGroupItemBackground(vint, vint)#Group index out of range"); + auto group = groupedItemSource[groupIndex]; + CHECK_ERROR(group->GetItemValues() && 0 <= itemIndex && itemIndex < group->GetItemValues()->GetCount(), L"GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint, vint)#Item index out of range"); + + auto groupFlow = MenuGetGroupFlow(groupIndex); + auto groupFlowItem = groupFlow->GetFlowItems()[itemIndex]; + auto groupItemTemplate = groupFlowItem->Children()[0]; + auto groupItemBackground = dynamic_cast(groupItemTemplate->Children()[0]->GetAssociatedControl()); + CHECK_ERROR(groupItemBackground, L"GuiBindableRibbonGalleryList::MenuGetGroupHeader(vint, vint)#Internal error."); + return groupItemBackground; + } + + void GuiBindableRibbonGalleryList::StartPreview(vint index) + { + if (index != itemList->GetSelectedItemIndex()) + { + GuiItemEventArgs previewArgs(boundsComposition); + previewArgs.itemIndex = index; + PreviewStarted.Execute(previewArgs); + } + } + + void GuiBindableRibbonGalleryList::StopPreview(vint index) + { + if (index != itemList->GetSelectedItemIndex()) + { + GuiItemEventArgs previewArgs(boundsComposition); + previewArgs.itemIndex = index; + PreviewStopped.Execute(previewArgs); + } + } + + GuiMenu* GuiBindableRibbonGalleryList::ProvideDropdownMenu() + { + return GetSubMenu(); + } + + GuiBindableRibbonGalleryList::GuiBindableRibbonGalleryList(theme::ThemeName themeName) + :GuiRibbonGallery(themeName) + , GroupedDataSource(boundsComposition) + { + ItemTemplateChanged.SetAssociatedComposition(boundsComposition); + SelectionChanged.SetAssociatedComposition(boundsComposition); + PreviewStarted.SetAssociatedComposition(boundsComposition); + PreviewStopped.SetAssociatedComposition(boundsComposition); + ItemApplied.SetAssociatedComposition(boundsComposition); + subMenu = new GuiRibbonToolstripMenu(theme::ThemeName::RibbonToolstripMenu, this); + + { + layout = new ribbon_impl::GalleryResponsiveLayout; + layout->SetAlignmentToParent(Margin(0, 0, 0, 0)); + containerComposition->AddChild(layout); + + itemListArranger = new ribbon_impl::GalleryItemArranger(this); + itemList = new GuiBindableTextList(theme::ThemeName::RibbonGalleryItemList); + itemList->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + itemList->SetArranger(itemListArranger); + itemList->SetItemSource(joinedItemSource.GetWrapper()); + itemList->SelectionChanged.AttachMethod(this, &GuiBindableRibbonGalleryList::OnItemListSelectionChanged); + itemList->ItemMouseEnter.AttachMethod(this, &GuiBindableRibbonGalleryList::OnItemListItemMouseEnter); + itemList->ItemMouseLeave.AttachMethod(this, &GuiBindableRibbonGalleryList::OnItemListItemMouseLeave); + layout->AddChild(itemList->GetBoundsComposition()); + } + { + groupContainer = new GuiScrollContainer(theme::ThemeName::ScrollView); + groupContainer->SetHorizontalAlwaysVisible(false); + groupContainer->SetVerticalAlwaysVisible(false); + groupContainer->SetExtendToFullWidth(true); + groupContainer->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + subMenu->GetContentComposition()->AddChild(groupContainer->GetBoundsComposition()); + + groupStack = new GuiRepeatStackComposition(); + groupStack->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + groupStack->SetAlignmentToParent(Margin(0, 0, 0, 0)); + groupStack->SetDirection(GuiStackComposition::Vertical); + groupStack->SetItemSource(groupedItemSource.GetWrapper()); + groupContainer->GetContainerComposition()->AddChild(groupStack); + MenuResetGroupTemplate(); + } + + RequestedScrollUp.AttachMethod(this, &GuiBindableRibbonGalleryList::OnRequestedScrollUp); + RequestedScrollDown.AttachMethod(this, &GuiBindableRibbonGalleryList::OnRequestedScrollDown); + RequestedDropdown.AttachMethod(this, &GuiBindableRibbonGalleryList::OnRequestedDropdown); + boundsComposition->BoundsChanged.AttachMethod(this, &GuiBindableRibbonGalleryList::OnBoundsChanged); + itemListArranger->UnblockScrollUpdate(); + } + + GuiBindableRibbonGalleryList::~GuiBindableRibbonGalleryList() + { + delete subMenu; + } + + GuiBindableRibbonGalleryList::ItemStyleProperty GuiBindableRibbonGalleryList::GetItemTemplate() + { + return itemStyle; + } + + void GuiBindableRibbonGalleryList::SetItemTemplate(ItemStyleProperty value) + { + if (itemStyle != value) + { + itemStyle = value; + itemList->SetItemTemplate(value); + ItemTemplateChanged.Execute(GetNotifyEventArguments()); + } + } + + GalleryPos GuiBindableRibbonGalleryList::IndexToGalleryPos(vint index) + { + if (0 <= index && index < joinedItemSource.Count()) + { + FOREACH_INDEXER(Ptr, group, groupIndex, groupedItemSource) + { + auto itemValues = group->GetItemValues(); + vint itemCount = itemValues ? itemValues->GetCount() : 0; + if (index >= itemCount) + { + index -= itemCount; + } + else + { + return GalleryPos(groupIndex, index); + } + } + } + return {}; + } + + vint GuiBindableRibbonGalleryList::GalleryPosToIndex(GalleryPos pos) + { + if (0 <= pos.group && pos.group < groupedItemSource.Count()) + { + auto countBeforeGroup = GetCountBeforeGroup(pos.group); + auto itemValues = groupedItemSource[pos.group]->GetItemValues(); + vint itemCount = itemValues ? itemValues->GetCount() : 0; + if (0 <= pos.item && pos.item < itemCount) + { + return countBeforeGroup + pos.item; + } + } + return -1; + } + + vint GuiBindableRibbonGalleryList::GetMinCount() + { + return layout->GetMinCount(); + } + + void GuiBindableRibbonGalleryList::SetMinCount(vint value) + { + layout->SetMinCount(value); + } + + vint GuiBindableRibbonGalleryList::GetMaxCount() + { + return layout->GetMaxCount(); + } + + void GuiBindableRibbonGalleryList::SetMaxCount(vint value) + { + layout->SetMaxCount(value); + } + + vint GuiBindableRibbonGalleryList::GetSelectedIndex() + { + return itemList->GetSelectedItemIndex(); + } + + description::Value GuiBindableRibbonGalleryList::GetSelectedItem() + { + vint index = itemList->GetSelectedItemIndex(); + if (index == -1) return Value(); + + auto pos = IndexToGalleryPos(index); + return groupedItemSource[pos.group]->GetItemValues()->Get(pos.item); + } + + void GuiBindableRibbonGalleryList::ApplyItem(vint index) + { + if (index == -1) + { + itemList->ClearSelection(); + } + else + { + itemList->SetSelected(index, true); + itemList->EnsureItemVisible(index); + } + } + + void GuiBindableRibbonGalleryList::SelectItem(vint index) + { + skipItemAppliedEvent = true; + ApplyItem(index); + skipItemAppliedEvent = false; + } + + vint GuiBindableRibbonGalleryList::GetVisibleItemCount() + { + return visibleItemCount; + } + + void GuiBindableRibbonGalleryList::SetVisibleItemCount(vint value) + { + if (visibleItemCount != value) + { + visibleItemCount = value; + UpdateLayoutSizeOffset(); + } + } + + GuiToolstripMenu* GuiBindableRibbonGalleryList::GetSubMenu() + { + return subMenu; + } + + IDescriptable* GuiBindableRibbonGalleryList::QueryService(const WString& identifier) + { + if (identifier == IGuiMenuDropdownProvider::Identifier) + { + return (IGuiMenuDropdownProvider*)this; + } + else + { + return GuiRibbonGallery::QueryService(identifier); + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TOOLSTRIPPACKAGE\GUIRIBBONIMPL.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace compositions; + +/*********************************************************************** +GalleryItemArranger +***********************************************************************/ + + namespace ribbon_impl + { + void GalleryItemArranger::BeginPlaceItem(bool forMoving, Rect newBounds, vint& newStartIndex) + { + if (forMoving) + { + pim_itemWidth = itemWidth; + newStartIndex = firstIndex; + } + } + + void GalleryItemArranger::PlaceItem(bool forMoving, vint index, ItemStyleRecord style, Rect viewBounds, Rect& bounds, Margin& alignmentToParent) + { + alignmentToParent = Margin(-1, 0, -1, 0); + bounds = Rect(Point((index - firstIndex) * itemWidth, 0), Size(itemWidth, 0)); + + if (forMoving) + { + vint styleWidth = callback->GetStylePreferredSize(GetStyleBounds(style)).x; + if (pim_itemWidth < styleWidth) + { + pim_itemWidth = styleWidth; + } + } + } + + bool GalleryItemArranger::IsItemOutOfViewBounds(vint index, ItemStyleRecord style, Rect bounds, Rect viewBounds) + { + return bounds.Right() + pim_itemWidth > viewBounds.Right(); + } + + bool GalleryItemArranger::EndPlaceItem(bool forMoving, Rect newBounds, vint newStartIndex) + { + bool result = false; + if (forMoving) + { + if (pim_itemWidth != itemWidth) + { + itemWidth = pim_itemWidth; + result = true; + } + } + + if (!blockScrollUpdate) + { + UnblockScrollUpdate(); + } + + return result; + } + + void GalleryItemArranger::InvalidateItemSizeCache() + { + itemWidth = 1; + } + + Size GalleryItemArranger::OnCalculateTotalSize() + { + return Size(1, 1); + } + + GalleryItemArranger::GalleryItemArranger(GuiBindableRibbonGalleryList* _owner) + :owner(_owner) + { + } + + GalleryItemArranger::~GalleryItemArranger() + { + } + + vint GalleryItemArranger::FindItem(vint itemIndex, compositions::KeyDirection key) + { + vint count = itemProvider->Count(); + vint groupCount = viewBounds.Width() / itemWidth; + + switch (key) + { + case KeyDirection::Left: + itemIndex--; + break; + case KeyDirection::Right: + itemIndex++; + break; + case KeyDirection::Home: + itemIndex = 0; + break; + case KeyDirection::End: + itemIndex = count; + break; + case KeyDirection::PageUp: + itemIndex -= groupCount; + break; + case KeyDirection::PageDown: + itemIndex += groupCount; + break; + default: + return -1; + } + + if (itemIndex < 0) return 0; + else if (itemIndex >= count) return count - 1; + else return itemIndex; + } + + bool GalleryItemArranger::EnsureItemVisible(vint itemIndex) + { + if (callback && 0 <= itemIndex && itemIndex < itemProvider->Count()) + { + vint groupCount = viewBounds.Width() / itemWidth; + if (itemIndex < firstIndex) + { + firstIndex = itemIndex; + callback->OnTotalSizeChanged(); + } + else if (itemIndex >= firstIndex + groupCount) + { + firstIndex = itemIndex - groupCount + 1; + callback->OnTotalSizeChanged(); + } + return true; + } + return false; + } + + Size GalleryItemArranger::GetAdoptedSize(Size expectedSize) + { + return Size(1, 1); + } + + void GalleryItemArranger::ScrollUp() + { + vint count = itemProvider->Count(); + vint groupCount = viewBounds.Width() / itemWidth; + if (count > groupCount) + { + firstIndex -= groupCount; + if (firstIndex < 0) + { + firstIndex = 0; + } + + if (callback) + { + callback->OnTotalSizeChanged(); + } + } + } + + void GalleryItemArranger::ScrollDown() + { + vint count = itemProvider->Count(); + vint groupCount = viewBounds.Width() / itemWidth; + if (count > groupCount) + { + firstIndex += groupCount; + if (firstIndex > count - groupCount) + { + firstIndex = count - groupCount; + } + + if (callback) + { + callback->OnTotalSizeChanged(); + } + } + } + + void GalleryItemArranger::UnblockScrollUpdate() + { + blockScrollUpdate = false; + + vint count = itemProvider->Count(); + vint groupCount = viewBounds.Width() / pim_itemWidth; + owner->SetScrollUpEnabled(firstIndex > 0); + owner->SetScrollDownEnabled(firstIndex + groupCount < count); + if (owner->layout->GetItemWidth() != pim_itemWidth) + { + owner->layout->SetItemWidth(pim_itemWidth); + owner->UpdateLayoutSizeOffset(); + } + } + +/*********************************************************************** +GalleryResponsiveLayout +***********************************************************************/ + + void GalleryResponsiveLayout::UpdateMinSize() + { + SetPreferredMinSize(Size(itemCount * itemWidth + sizeOffset.x, sizeOffset.y)); + } + + GalleryResponsiveLayout::GalleryResponsiveLayout() + { + SetDirection(ResponsiveDirection::Horizontal); + } + + GalleryResponsiveLayout::~GalleryResponsiveLayout() + { + } + + vint GalleryResponsiveLayout::GetMinCount() + { + return minCount; + } + + vint GalleryResponsiveLayout::GetMaxCount() + { + return maxCount; + } + + vint GalleryResponsiveLayout::GetItemWidth() + { + return itemWidth; + } + + Size GalleryResponsiveLayout::GetSizeOffset() + { + return sizeOffset; + } + + vint GalleryResponsiveLayout::GetVisibleItemCount() + { + return itemCount; + } + + void GalleryResponsiveLayout::SetMinCount(vint value) + { + vint oldCount = GetLevelCount(); + vint oldLevel = GetCurrentLevel(); + + if (minCount != value) + { + if (value < 0) value = 0; + minCount = value; + if (maxCount < minCount) maxCount = minCount; + if (itemCount < minCount) itemCount = minCount; + UpdateMinSize(); + } + + bool countChanged = oldCount != GetLevelCount(); + bool levelChanged = oldLevel != GetCurrentLevel(); + if (countChanged) LevelCountChanged.Execute(GuiEventArgs(this)); + if (levelChanged) CurrentLevelChanged.Execute(GuiEventArgs(this)); + if (countChanged || levelChanged) OnResponsiveChildLevelUpdated(); + } + + void GalleryResponsiveLayout::SetMaxCount(vint value) + { + vint oldCount = GetLevelCount(); + vint oldLevel = GetCurrentLevel(); + + if (maxCount != value) + { + if (value < 0) value = 0; + maxCount = value; + if (minCount > maxCount) minCount = maxCount; + if (itemCount > maxCount) itemCount = maxCount; + UpdateMinSize(); + } + + if (oldCount != GetLevelCount()) LevelCountChanged.Execute(GuiEventArgs(this)); + if (oldLevel != GetCurrentLevel()) CurrentLevelChanged.Execute(GuiEventArgs(this)); + } + + void GalleryResponsiveLayout::SetItemWidth(vint value) + { + if (itemWidth != value) + { + itemWidth = value; + UpdateMinSize(); + } + } + + void GalleryResponsiveLayout::SetSizeOffset(Size value) + { + if (sizeOffset != value) + { + sizeOffset = value; + UpdateMinSize(); + } + } + + vint GalleryResponsiveLayout::GetLevelCount() + { + return maxCount - minCount + 1; + } + + vint GalleryResponsiveLayout::GetCurrentLevel() + { + return itemCount - minCount; + } + + bool GalleryResponsiveLayout::LevelDown() + { + if (itemCount > minCount) + { + itemCount--; + UpdateMinSize(); + CurrentLevelChanged.Execute(GuiEventArgs(this)); + return true; + } + return false; + } + + bool GalleryResponsiveLayout::LevelUp() + { + if (itemCount < maxCount) + { + itemCount++; + UpdateMinSize(); + CurrentLevelChanged.Execute(GuiEventArgs(this)); + return true; + } + return false; + } + } + } + } +} + +/*********************************************************************** +.\CONTROLS\TOOLSTRIPPACKAGE\GUITOOLSTRIPCOMMAND.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace compositions; + using namespace regex; + using namespace parsing; + +/*********************************************************************** +GuiToolstripCommand +***********************************************************************/ + + void GuiToolstripCommand::OnShortcutKeyItemExecuted(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + Executed.ExecuteWithNewSender(arguments, sender); + } + + void GuiToolstripCommand::OnRenderTargetChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + UpdateShortcutOwner(); + } + + void GuiToolstripCommand::InvokeDescriptionChanged() + { + GuiEventArgs arguments; + DescriptionChanged.Execute(arguments); + } + + void GuiToolstripCommand::ReplaceShortcut(compositions::IGuiShortcutKeyItem* value, Ptr builder) + { + if (shortcutKeyItem != value) + { + if (shortcutKeyItem) + { + shortcutKeyItem->Executed.Detach(shortcutKeyItemExecutedHandler); + if (shortcutBuilder) + { + auto manager = dynamic_cast(shortcutOwner->GetShortcutKeyManager()); + if (manager) + { + manager->DestroyShortcut(shortcutBuilder->ctrl, shortcutBuilder->shift, shortcutBuilder->alt, shortcutBuilder->key); + } + } + } + shortcutKeyItem = nullptr; + shortcutKeyItemExecutedHandler = nullptr; + shortcutBuilder = value ? builder : nullptr; + if (value) + { + shortcutKeyItem = value; + shortcutKeyItemExecutedHandler = shortcutKeyItem->Executed.AttachMethod(this, &GuiToolstripCommand::OnShortcutKeyItemExecuted); + } + InvokeDescriptionChanged(); + } + } + + void GuiToolstripCommand::BuildShortcut(const WString& builderText) + { + List> errors; + if (auto parser = GetParserManager()->GetParser(L"SHORTCUT")) + { + if (Ptr builder = parser->ParseInternal(builderText, errors)) + { + if (shortcutOwner) + { + if (!shortcutOwner->GetShortcutKeyManager()) + { + shortcutOwner->SetShortcutKeyManager(new GuiShortcutKeyManager); + } + if (auto manager = dynamic_cast(shortcutOwner->GetShortcutKeyManager())) + { + IGuiShortcutKeyItem* item = manager->TryGetShortcut(builder->ctrl, builder->shift, builder->alt, builder->key); + if (!item) + { + item = manager->CreateShortcut(builder->ctrl, builder->shift, builder->alt, builder->key); + if (item) + { + ReplaceShortcut(item, builder); + } + } + } + } + else + { + shortcutBuilder = builder; + } + } + } + } + + void GuiToolstripCommand::UpdateShortcutOwner() + { + GuiControlHost* host = nullptr; + if (auto control = dynamic_cast(attachedRootObject)) + { + host = control->GetRelatedControlHost(); + } + else if (auto composition = dynamic_cast(attachedRootObject)) + { + host = composition->GetRelatedControlHost(); + } + + if (shortcutOwner != host) + { + if (shortcutOwner) + { + ReplaceShortcut(nullptr, nullptr); + shortcutOwner = nullptr; + } + shortcutOwner = host; + if (shortcutBuilder && !shortcutKeyItem) + { + BuildShortcut(shortcutBuilder->text); + } + } + } + + GuiToolstripCommand::GuiToolstripCommand() + { + } + + GuiToolstripCommand::~GuiToolstripCommand() + { + } + + void GuiToolstripCommand::Attach(GuiInstanceRootObject* rootObject) + { + GuiGraphicsComposition* rootComposition = nullptr; + + if (attachedRootObject != rootObject) + { + if (attachedRootObject) + { + if (auto control = dynamic_cast(attachedRootObject)) + { + control->RenderTargetChanged.Detach(renderTargetChangedHandler); + } + else if (auto composition = dynamic_cast(attachedRootObject)) + { + composition->GetEventReceiver()->renderTargetChanged.Detach(renderTargetChangedHandler); + } + renderTargetChangedHandler = nullptr; + } + + attachedRootObject = rootObject; + if (attachedRootObject) + { + if (auto control = dynamic_cast(attachedRootObject)) + { + renderTargetChangedHandler = control->RenderTargetChanged.AttachMethod(this, &GuiToolstripCommand::OnRenderTargetChanged); + } + else if (auto composition = dynamic_cast(attachedRootObject)) + { + renderTargetChangedHandler = composition->GetEventReceiver()->renderTargetChanged.AttachMethod(this, &GuiToolstripCommand::OnRenderTargetChanged); + } + } + UpdateShortcutOwner(); + } + } + + void GuiToolstripCommand::Detach(GuiInstanceRootObject* rootObject) + { + Attach(nullptr); + } + + Ptr GuiToolstripCommand::GetLargeImage() + { + return largeImage; + } + + void GuiToolstripCommand::SetLargeImage(Ptr value) + { + if (largeImage != value) + { + largeImage = value; + InvokeDescriptionChanged(); + } + } + + Ptr GuiToolstripCommand::GetImage() + { + return image; + } + + void GuiToolstripCommand::SetImage(Ptr value) + { + if(image!=value) + { + image=value; + InvokeDescriptionChanged(); + } + } + + const WString& GuiToolstripCommand::GetText() + { + return text; + } + + void GuiToolstripCommand::SetText(const WString& value) + { + if(text!=value) + { + text=value; + InvokeDescriptionChanged(); + } + } + + compositions::IGuiShortcutKeyItem* GuiToolstripCommand::GetShortcut() + { + return shortcutKeyItem; + } + + void GuiToolstripCommand::SetShortcut(compositions::IGuiShortcutKeyItem* value) + { + ReplaceShortcut(value, 0); + } + + WString GuiToolstripCommand::GetShortcutBuilder() + { + return shortcutBuilder ? shortcutBuilder->text : L""; + } + + void GuiToolstripCommand::SetShortcutBuilder(const WString& value) + { + BuildShortcut(value); + } + + bool GuiToolstripCommand::GetEnabled() + { + return enabled; + } + + void GuiToolstripCommand::SetEnabled(bool value) + { + if(enabled!=value) + { + enabled=value; + InvokeDescriptionChanged(); + } + } + + bool GuiToolstripCommand::GetSelected() + { + return selected; + } + + void GuiToolstripCommand::SetSelected(bool value) + { + if(selected!=value) + { + selected=value; + InvokeDescriptionChanged(); + } + } + +/*********************************************************************** +GuiToolstripCommand::ShortcutBuilder Parser +***********************************************************************/ + + class GuiToolstripCommandShortcutParser : public Object, public IGuiParser + { + typedef GuiToolstripCommand::ShortcutBuilder ShortcutBuilder; + public: + Regex regexShortcut; + + GuiToolstripCommandShortcutParser() + :regexShortcut(L"((Ctrl)/+|(Shift)/+|(Alt)/+)*(/.+)") + { + } + + Ptr ParseInternal(const WString& text, collections::List>& errors)override + { + Ptr match=regexShortcut.MatchHead(text); + if (match && match->Result().Length() != text.Length()) + { + errors.Add(new ParsingError(L"Failed to parse a shortcut \"" + text + L"\".")); + return 0; + } + + Ptr builder = new ShortcutBuilder; + builder->text = text; + builder->ctrl = match->Groups().Contains(L"ctrl"); + builder->shift = match->Groups().Contains(L"shift"); + builder->alt = match->Groups().Contains(L"alt"); + + WString name = match->Groups()[L"key"][0].Value(); + builder->key = GetCurrentController()->InputService()->GetKey(name); + + return builder->key == -1 ? nullptr : builder; + } + }; + +/*********************************************************************** +GuiToolstripCommandPlugin +***********************************************************************/ + + class GuiToolstripCommandPlugin : public Object, public IGuiPlugin + { + public: + + GUI_PLUGIN_NAME(GacUI_Compiler_ShortcutParser) + { + GUI_PLUGIN_DEPEND(GacUI_Parser); + } + + void Load()override + { + IGuiParserManager* manager=GetParserManager(); + manager->SetParser(L"SHORTCUT", new GuiToolstripCommandShortcutParser); + } + + void Unload()override + { + } + }; + GUI_REGISTER_PLUGIN(GuiToolstripCommandPlugin) + } + } +} + +/*********************************************************************** +.\CONTROLS\TOOLSTRIPPACKAGE\GUITOOLSTRIPMENU.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace controls + { + using namespace collections; + using namespace compositions; + +/*********************************************************************** +GuiToolstripCollectionBase +***********************************************************************/ + + const wchar_t* const IToolstripUpdateLayoutInvoker::Identifier = L"vl::presentation::controls::IToolstripUpdateLayoutInvoker"; + + void GuiToolstripCollectionBase::InvokeUpdateLayout() + { + if(contentCallback) + { + contentCallback->UpdateLayout(); + } + } + + bool GuiToolstripCollectionBase::QueryInsert(vint index, GuiControl* const& child) + { + return !items.Contains(child) && !child->GetBoundsComposition()->GetParent(); + } + + void GuiToolstripCollectionBase::BeforeRemove(vint index, GuiControl* const& child) + { + if (auto invoker = child->QueryTypedService()) + { + invoker->SetCallback(nullptr); + } + InvokeUpdateLayout(); + } + + void GuiToolstripCollectionBase::AfterInsert(vint index, GuiControl* const& child) + { + if (auto invoker = child->QueryTypedService()) + { + invoker->SetCallback(contentCallback); + } + InvokeUpdateLayout(); + } + + void GuiToolstripCollectionBase::AfterRemove(vint index, vint count) + { + InvokeUpdateLayout(); + } + + GuiToolstripCollectionBase::GuiToolstripCollectionBase(IToolstripUpdateLayout* _contentCallback) + :contentCallback(_contentCallback) + { + } + + GuiToolstripCollectionBase::~GuiToolstripCollectionBase() + { + } + +/*********************************************************************** +GuiToolstripCollection +***********************************************************************/ + + void GuiToolstripCollection::UpdateItemVisibility(vint index, GuiControl* child) + { + auto stackItem = stackComposition->GetStackItems()[index]; + if (child->GetVisible()) + { + stackItem->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + child->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + } + else + { + stackItem->SetMinSizeLimitation(GuiGraphicsComposition::NoLimit); + child->GetBoundsComposition()->SetAlignmentToParent(Margin(-1, -1, -1, -1)); + } + } + + void GuiToolstripCollection::OnItemVisibleChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + auto child = sender->GetRelatedControl(); + vint index = IndexOf(child); + UpdateItemVisibility(index, child); + InvokeUpdateLayout(); + } + + void GuiToolstripCollection::BeforeRemove(vint index, GuiControl* const& child) + { + GuiStackItemComposition* stackItem = stackComposition->GetStackItems().Get(index); + + auto eventHandler = eventHandlers[index]; + child->VisibleChanged.Detach(eventHandler); + eventHandlers.RemoveAt(index); + + GuiToolstripCollectionBase::BeforeRemove(index, child); + } + + void GuiToolstripCollection::AfterInsert(vint index, GuiControl* const& child) + { + { + GuiStackItemComposition* stackItem = new GuiStackItemComposition; + stackItem->AddChild(child->GetBoundsComposition()); + stackComposition->InsertStackItem(index, stackItem); + } + { + auto eventHandler = child->VisibleChanged.AttachMethod(this, &GuiToolstripCollection::OnItemVisibleChanged); + eventHandlers.Insert(index, eventHandler); + } + UpdateItemVisibility(index, child); + GuiToolstripCollectionBase::AfterInsert(index, child); + } + + void GuiToolstripCollection::AfterRemove(vint index, vint count) + { + for (vint i = 0; i < count; i++) + { + auto stackItem = stackComposition->GetStackItems().Get(index); + stackComposition->RemoveChild(stackItem); + SafeDeleteComposition(stackItem); + } + GuiToolstripCollectionBase::AfterRemove(index, count); + } + + GuiToolstripCollection::GuiToolstripCollection(IToolstripUpdateLayout* _contentCallback, compositions::GuiStackComposition* _stackComposition) + :GuiToolstripCollectionBase(_contentCallback) + ,stackComposition(_stackComposition) + { + } + + GuiToolstripCollection::~GuiToolstripCollection() + { + } + +/*********************************************************************** +GuiToolstripMenu +***********************************************************************/ + + void GuiToolstripMenu::UpdateLayout() + { + sharedSizeRootComposition->ForceCalculateSizeImmediately(); + } + + GuiToolstripMenu::GuiToolstripMenu(theme::ThemeName themeName, GuiControl* _owner) + :GuiMenu(themeName, _owner) + { + sharedSizeRootComposition = new GuiSharedSizeRootComposition(); + sharedSizeRootComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + sharedSizeRootComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + containerComposition->AddChild(sharedSizeRootComposition); + + stackComposition=new GuiStackComposition; + stackComposition->SetDirection(GuiStackComposition::Vertical); + stackComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + stackComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + sharedSizeRootComposition->AddChild(stackComposition); + + toolstripItems = new GuiToolstripCollection(this, stackComposition); + } + + GuiToolstripMenu::~GuiToolstripMenu() + { + } + + collections::ObservableListBase& GuiToolstripMenu::GetToolstripItems() + { + return *toolstripItems.Obj(); + } + +/*********************************************************************** +GuiToolstripMenuBar +***********************************************************************/ + + GuiToolstripMenuBar::GuiToolstripMenuBar(theme::ThemeName themeName) + :GuiMenuBar(themeName) + { + stackComposition=new GuiStackComposition; + stackComposition->SetDirection(GuiStackComposition::Horizontal); + stackComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + stackComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + containerComposition->AddChild(stackComposition); + + toolstripItems=new GuiToolstripCollection(nullptr, stackComposition); + } + + GuiToolstripMenuBar::~GuiToolstripMenuBar() + { + } + + collections::ObservableListBase& GuiToolstripMenuBar::GetToolstripItems() + { + return *toolstripItems.Obj(); + } + +/*********************************************************************** +GuiToolstripToolBar +***********************************************************************/ + + GuiToolstripToolBar::GuiToolstripToolBar(theme::ThemeName themeName) + :GuiControl(themeName) + { + stackComposition=new GuiStackComposition; + stackComposition->SetDirection(GuiStackComposition::Horizontal); + stackComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + stackComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + containerComposition->AddChild(stackComposition); + + toolstripItems=new GuiToolstripCollection(nullptr, stackComposition); + } + + GuiToolstripToolBar::~GuiToolstripToolBar() + { + } + + collections::ObservableListBase& GuiToolstripToolBar::GetToolstripItems() + { + return *toolstripItems.Obj(); + } + +/*********************************************************************** +GuiToolstripButton +***********************************************************************/ + + void GuiToolstripButton::SetCallback(IToolstripUpdateLayout* _callback) + { + callback = _callback; + } + + void GuiToolstripButton::UpdateCommandContent() + { + if(command) + { + SetLargeImage(command->GetLargeImage()); + SetImage(command->GetImage()); + SetText(command->GetText()); + SetEnabled(command->GetEnabled()); + SetSelected(command->GetSelected()); + if(command->GetShortcut()) + { + SetShortcutText(command->GetShortcut()->GetName()); + } + else + { + SetShortcutText(L""); + } + } + else + { + SetLargeImage(nullptr); + SetImage(nullptr); + SetText(L""); + SetEnabled(true); + SetSelected(false); + SetShortcutText(L""); + } + } + + void GuiToolstripButton::OnLayoutAwaredPropertyChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if (callback) + { + callback->UpdateLayout(); + } + } + + void GuiToolstripButton::OnClicked(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + if(command) + { + command->Executed.ExecuteWithNewSender(arguments, sender); + } + } + + void GuiToolstripButton::OnCommandDescriptionChanged(compositions::GuiGraphicsComposition* sender, compositions::GuiEventArgs& arguments) + { + UpdateCommandContent(); + } + + GuiToolstripButton::GuiToolstripButton(theme::ThemeName themeName) + :GuiMenuButton(themeName) + ,command(0) + { + Clicked.AttachMethod(this, &GuiToolstripButton::OnClicked); + TextChanged.AttachMethod(this, &GuiToolstripButton::OnLayoutAwaredPropertyChanged); + ShortcutTextChanged.AttachMethod(this, &GuiToolstripButton::OnLayoutAwaredPropertyChanged); + } + + GuiToolstripButton::~GuiToolstripButton() + { + } + + GuiToolstripCommand* GuiToolstripButton::GetCommand() + { + return command; + } + + void GuiToolstripButton::SetCommand(GuiToolstripCommand* value) + { + if(command!=value) + { + if(command) + { + command->DescriptionChanged.Detach(descriptionChangedHandler); + } + command=0; + descriptionChangedHandler=0; + if(value) + { + command=value; + descriptionChangedHandler=command->DescriptionChanged.AttachMethod(this, &GuiToolstripButton::OnCommandDescriptionChanged); + } + UpdateCommandContent(); + } + } + + GuiToolstripMenu* GuiToolstripButton::GetToolstripSubMenu() + { + return dynamic_cast(GetSubMenu()); + } + + GuiToolstripMenu* GuiToolstripButton::EnsureToolstripSubMenu() + { + if (!GetSubMenu()) + { + CreateToolstripSubMenu({}); + } + return dynamic_cast(GetSubMenu()); + } + + void GuiToolstripButton::CreateToolstripSubMenu(TemplateProperty subMenuTemplate) + { + if (!subMenu) + { + auto newSubMenu = new GuiToolstripMenu(theme::ThemeName::Menu, this); + if (subMenuTemplate) + { + newSubMenu->SetControlTemplate(subMenuTemplate); + } + else + { + newSubMenu->SetControlTemplate(GetControlTemplateObject(true)->GetSubMenuTemplate()); + } + SetSubMenu(newSubMenu, true); + } + } + + IDescriptable* GuiToolstripButton::QueryService(const WString& identifier) + { + if (identifier == IToolstripUpdateLayoutInvoker::Identifier) + { + return (IToolstripUpdateLayoutInvoker*)this; + } + else + { + return GuiMenuButton::QueryService(identifier); + } + } + +/*********************************************************************** +GuiToolstripNestedContainer +***********************************************************************/ + + void GuiToolstripNestedContainer::UpdateLayout() + { + if (callback) + { + callback->UpdateLayout(); + } + } + + void GuiToolstripNestedContainer::SetCallback(IToolstripUpdateLayout* _callback) + { + callback = _callback; + } + + GuiToolstripNestedContainer::GuiToolstripNestedContainer(theme::ThemeName themeName) + :GuiControl(themeName) + { + } + + GuiToolstripNestedContainer::~GuiToolstripNestedContainer() + { + } + + IDescriptable* GuiToolstripNestedContainer::QueryService(const WString& identifier) + { + if (identifier == IToolstripUpdateLayoutInvoker::Identifier) + { + return (IToolstripUpdateLayoutInvoker*)this; + } + else + { + return GuiControl::QueryService(identifier); + } + } + +/*********************************************************************** +GuiToolstripGroupContainer::GroupCollection +***********************************************************************/ + + void GuiToolstripGroupContainer::GroupCollection::BeforeRemove(vint index, GuiControl* const& child) + { + auto controlStackItem = container->stackComposition->GetStackItems()[index * 2]; + CHECK_ERROR(controlStackItem->RemoveChild(child->GetBoundsComposition()), L"GuiToolstripGroupContainer::GroupCollection::BeforeRemove(vint, GuiControl* const&)#Internal error"); + } + + void GuiToolstripGroupContainer::GroupCollection::AfterInsert(vint index, GuiControl* const& child) + { + auto controlStackItem = new GuiStackItemComposition; + child->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + CHECK_ERROR(controlStackItem->AddChild(child->GetBoundsComposition()), L"GuiToolstripGroupContainer::GroupCollection::AfterInsert(vint, GuiControl* const&)#Internal error"); + + if (container->stackComposition->GetStackItems().Count() > 0) + { + auto splitterStackItem = new GuiStackItemComposition; + auto splitter = new GuiControl(container->splitterThemeName); + if (splitterTemplate) + { + splitter->SetControlTemplate(splitterTemplate); + } + splitter->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + splitterStackItem->AddChild(splitter->GetBoundsComposition()); + container->stackComposition->InsertStackItem(index == 0 ? 0 : index * 2 - 1, splitterStackItem); + } + + container->stackComposition->InsertStackItem(index * 2, controlStackItem); + + GuiToolstripCollectionBase::AfterInsert(index, child); + } + + void GuiToolstripGroupContainer::GroupCollection::AfterRemove(vint index, vint count) + { + vint min = index * 2; + vint max = (index + count - 1) * 2; + for (vint i = min; i <= max; i++) + { + auto stackItem = container->stackComposition->GetStackItems()[min]; + container->stackComposition->RemoveChild(stackItem); + SafeDeleteComposition(stackItem); + } + GuiToolstripCollectionBase::AfterRemove(index, count); + } + + GuiToolstripGroupContainer::GroupCollection::GroupCollection(GuiToolstripGroupContainer* _container) + :GuiToolstripCollectionBase(_container) + , container(_container) + { + } + + GuiToolstripGroupContainer::GroupCollection::~GroupCollection() + { + } + + GuiToolstripGroupContainer::ControlTemplatePropertyType GuiToolstripGroupContainer::GroupCollection::GetSplitterTemplate() + { + return splitterTemplate; + } + + void GuiToolstripGroupContainer::GroupCollection::SetSplitterTemplate(const ControlTemplatePropertyType& value) + { + splitterTemplate = value; + RebuildSplitters(); + } + + void GuiToolstripGroupContainer::GroupCollection::RebuildSplitters() + { + auto stack = container->stackComposition; + vint count = stack->GetStackItems().Count(); + for (vint i = 1; i < count; i += 2) + { + auto stackItem = stack->GetStackItems()[i]; + { + auto control = stackItem->Children()[0]->GetAssociatedControl(); + CHECK_ERROR(control != nullptr, L"GuiToolstripGroupContainer::GroupCollection::RebuildSplitters()#Internal error"); + stackItem->RemoveChild(control->GetBoundsComposition()); + delete control; + } + { + auto control = new GuiControl(container->splitterThemeName); + if (splitterTemplate) + { + control->SetControlTemplate(splitterTemplate); + } + control->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + stackItem->AddChild(control->GetBoundsComposition()); + } + } + } + +/*********************************************************************** +GuiToolstripGroupContainer +***********************************************************************/ + + void GuiToolstripGroupContainer::OnParentLineChanged() + { + auto direction = GuiStackComposition::Horizontal; + if (auto service = QueryTypedService()) + { + if (service->GetPreferredDirection() == IGuiMenuService::Vertical) + { + direction = GuiStackComposition::Vertical; + } + } + + if (direction != stackComposition->GetDirection()) + { + if (direction == GuiStackComposition::Vertical) + { + splitterThemeName = theme::ThemeName::MenuSplitter; + } + else + { + splitterThemeName = theme::ThemeName::ToolstripSplitter; + } + + stackComposition->SetDirection(direction); + splitterThemeName = splitterThemeName; + groupCollection->RebuildSplitters(); + UpdateLayout(); + } + + GuiControl::OnParentLineChanged(); + } + + GuiToolstripGroupContainer::GuiToolstripGroupContainer(theme::ThemeName themeName) + :GuiToolstripNestedContainer(themeName) + , splitterThemeName(theme::ThemeName::ToolstripSplitter) + { + stackComposition = new GuiStackComposition; + stackComposition->SetDirection(GuiStackComposition::Horizontal); + stackComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + stackComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + containerComposition->AddChild(stackComposition); + + groupCollection = new GroupCollection(this); + } + + GuiToolstripGroupContainer::~GuiToolstripGroupContainer() + { + } + + GuiToolstripGroupContainer::ControlTemplatePropertyType GuiToolstripGroupContainer::GetSplitterTemplate() + { + return groupCollection->GetSplitterTemplate(); + } + + void GuiToolstripGroupContainer::SetSplitterTemplate(const ControlTemplatePropertyType& value) + { + groupCollection->SetSplitterTemplate(value); + } + + collections::ObservableListBase& GuiToolstripGroupContainer::GetToolstripItems() + { + return *groupCollection.Obj(); + } + +/*********************************************************************** +GuiToolstripGroup +***********************************************************************/ + + void GuiToolstripGroup::OnParentLineChanged() + { + auto direction = GuiStackComposition::Horizontal; + if (auto service = QueryTypedService()) + { + if (service->GetPreferredDirection() == IGuiMenuService::Vertical) + { + direction = GuiStackComposition::Vertical; + } + } + + if (direction != stackComposition->GetDirection()) + { + stackComposition->SetDirection(direction); + UpdateLayout(); + } + + GuiControl::OnParentLineChanged(); + } + + GuiToolstripGroup::GuiToolstripGroup(theme::ThemeName themeName) + :GuiToolstripNestedContainer(themeName) + { + stackComposition = new GuiStackComposition; + stackComposition->SetDirection(GuiStackComposition::Horizontal); + stackComposition->SetAlignmentToParent(Margin(0, 0, 0, 0)); + stackComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + containerComposition->AddChild(stackComposition); + + toolstripItems = new GuiToolstripCollection(nullptr, stackComposition); + } + + GuiToolstripGroup::~GuiToolstripGroup() + { + } + + collections::ObservableListBase& GuiToolstripGroup::GetToolstripItems() + { + return *toolstripItems.Obj(); + } + } + } +} + +/*********************************************************************** +.\GRAPHICSCOMPOSITION\GUIGRAPHICSAXIS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + +/*********************************************************************** +GuiDefaultAxis +***********************************************************************/ + + GuiDefaultAxis::GuiDefaultAxis() + { + } + + GuiDefaultAxis::~GuiDefaultAxis() + { + } + + Size GuiDefaultAxis::RealSizeToVirtualSize(Size size) + { + return size; + } + + Size GuiDefaultAxis::VirtualSizeToRealSize(Size size) + { + return size; + } + + Point GuiDefaultAxis::RealPointToVirtualPoint(Size realFullSize, Point point) + { + return point; + } + + Point GuiDefaultAxis::VirtualPointToRealPoint(Size realFullSize, Point point) + { + return point; + } + + Rect GuiDefaultAxis::RealRectToVirtualRect(Size realFullSize, Rect rect) + { + return rect; + } + + Rect GuiDefaultAxis::VirtualRectToRealRect(Size realFullSize, Rect rect) + { + return rect; + } + + Margin GuiDefaultAxis::RealMarginToVirtualMargin(Margin margin) + { + return margin; + } + + Margin GuiDefaultAxis::VirtualMarginToRealMargin(Margin margin) + { + return margin; + } + + KeyDirection GuiDefaultAxis::RealKeyDirectionToVirtualKeyDirection(KeyDirection key) + { + return key; + } + +/*********************************************************************** +GuiAxis +***********************************************************************/ + + GuiAxis::GuiAxis(AxisDirection _axisDirection) + :axisDirection(_axisDirection) + { + } + + GuiAxis::~GuiAxis() + { + } + + AxisDirection GuiAxis::GetDirection() + { + return axisDirection; + } + + Size GuiAxis::RealSizeToVirtualSize(Size size) + { + switch(axisDirection) + { + case AxisDirection::LeftDown: + case AxisDirection::RightDown: + case AxisDirection::LeftUp: + case AxisDirection::RightUp: + return Size(size.x, size.y); + case AxisDirection::DownLeft: + case AxisDirection::DownRight: + case AxisDirection::UpLeft: + case AxisDirection::UpRight: + return Size(size.y, size.x); + } + return size; + } + + Size GuiAxis::VirtualSizeToRealSize(Size size) + { + return RealSizeToVirtualSize(size); + } + + Point GuiAxis::RealPointToVirtualPoint(Size realFullSize, Point point) + { + Rect rect(point, Size(0, 0)); + return RealRectToVirtualRect(realFullSize, rect).LeftTop(); + } + + Point GuiAxis::VirtualPointToRealPoint(Size realFullSize, Point point) + { + Rect rect(point, Size(0, 0)); + return VirtualRectToRealRect(realFullSize, rect).LeftTop(); + } + + Rect GuiAxis::RealRectToVirtualRect(Size realFullSize, Rect rect) + { + vint x1=rect.x1; + vint x2=realFullSize.x-rect.x2; + vint y1=rect.y1; + vint y2=realFullSize.y-rect.y2; + vint w=rect.Width(); + vint h=rect.Height(); + switch(axisDirection) + { + case AxisDirection::LeftDown: + return Rect(Point(x2, y1), Size(w, h)); + case AxisDirection::RightDown: + return Rect(Point(x1, y1), Size(w, h)); + case AxisDirection::LeftUp: + return Rect(Point(x2, y2), Size(w, h)); + case AxisDirection::RightUp: + return Rect(Point(x1, y2), Size(w, h)); + case AxisDirection::DownLeft: + return Rect(Point(y1, x2), Size(h, w)); + case AxisDirection::DownRight: + return Rect(Point(y1, x1), Size(h, w)); + case AxisDirection::UpLeft: + return Rect(Point(y2, x2), Size(h, w)); + case AxisDirection::UpRight: + return Rect(Point(y2, x1), Size(h, w)); + } + return rect; + } + + Rect GuiAxis::VirtualRectToRealRect(Size realFullSize, Rect rect) + { + realFullSize=RealSizeToVirtualSize(realFullSize); + vint x1=rect.x1; + vint x2=realFullSize.x-rect.x2; + vint y1=rect.y1; + vint y2=realFullSize.y-rect.y2; + vint w=rect.Width(); + vint h=rect.Height(); + switch(axisDirection) + { + case AxisDirection::LeftDown: + return Rect(Point(x2, y1), Size(w, h)); + case AxisDirection::RightDown: + return Rect(Point(x1, y1), Size(w, h)); + case AxisDirection::LeftUp: + return Rect(Point(x2, y2), Size(w, h)); + case AxisDirection::RightUp: + return Rect(Point(x1, y2), Size(w, h)); + case AxisDirection::DownLeft: + return Rect(Point(y2, x1), Size(h, w)); + case AxisDirection::DownRight: + return Rect(Point(y1, x1), Size(h, w)); + case AxisDirection::UpLeft: + return Rect(Point(y2, x2), Size(h, w)); + case AxisDirection::UpRight: + return Rect(Point(y1, x2), Size(h, w)); + } + return rect; + } + + Margin GuiAxis::RealMarginToVirtualMargin(Margin margin) + { + vint x1=margin.left; + vint x2=margin.right; + vint y1=margin.top; + vint y2=margin.bottom; + switch(axisDirection) + { + case AxisDirection::LeftDown: + return Margin(x2, y1, x1, y2); + case AxisDirection::RightDown: + return Margin(x1, y1, x2, y2); + case AxisDirection::LeftUp: + return Margin(x2, y2, x1, y1); + case AxisDirection::RightUp: + return Margin(x1, y2, x2, y1); + case AxisDirection::DownLeft: + return Margin(y1, x2, y2, x1); + case AxisDirection::DownRight: + return Margin(y1, x1, y2, x2); + case AxisDirection::UpLeft: + return Margin(y2, x2, y1, x1); + case AxisDirection::UpRight: + return Margin(y2, x1, y1, x2); + } + return margin; + } + + Margin GuiAxis::VirtualMarginToRealMargin(Margin margin) + { + vint x1=margin.left; + vint x2=margin.right; + vint y1=margin.top; + vint y2=margin.bottom; + switch(axisDirection) + { + case AxisDirection::LeftDown: + return Margin(x2, y1, x1, y2); + case AxisDirection::RightDown: + return Margin(x1, y1, x2, y2); + case AxisDirection::LeftUp: + return Margin(x2, y2, x1, y1); + case AxisDirection::RightUp: + return Margin(x1, y2, x2, y1); + case AxisDirection::DownLeft: + return Margin(y2, x1, y1, x2); + case AxisDirection::DownRight: + return Margin(y1, x1, y2, x2); + case AxisDirection::UpLeft: + return Margin(y2, x2, y1, x1); + case AxisDirection::UpRight: + return Margin(y1, x2, y2, x1); + default:; + } + return margin; + } + + KeyDirection GuiAxis::RealKeyDirectionToVirtualKeyDirection(KeyDirection key) + { + bool pageKey=false; + switch(key) + { + case KeyDirection::PageUp: + pageKey=true; + key=KeyDirection::Up; + break; + case KeyDirection::PageDown: + pageKey=true; + key=KeyDirection::Down; + break; + case KeyDirection::PageLeft: + pageKey=true; + key=KeyDirection::Left; + break; + case KeyDirection::PageRight: + pageKey=true; + key=KeyDirection::Right; + break; + default:; + } + + switch(key) + { + case KeyDirection::Up: + switch(axisDirection) + { + case AxisDirection::LeftDown: key=KeyDirection::Up; break; + case AxisDirection::RightDown: key=KeyDirection::Up; break; + case AxisDirection::LeftUp: key=KeyDirection::Down; break; + case AxisDirection::RightUp: key=KeyDirection::Down; break; + case AxisDirection::DownLeft: key=KeyDirection::Left; break; + case AxisDirection::DownRight: key=KeyDirection::Left; break; + case AxisDirection::UpLeft: key=KeyDirection::Right; break; + case AxisDirection::UpRight: key=KeyDirection::Right; break; + } + break; + case KeyDirection::Down: + switch(axisDirection) + { + case AxisDirection::LeftDown: key=KeyDirection::Down; break; + case AxisDirection::RightDown: key=KeyDirection::Down; break; + case AxisDirection::LeftUp: key=KeyDirection::Up; break; + case AxisDirection::RightUp: key=KeyDirection::Up; break; + case AxisDirection::DownLeft: key=KeyDirection::Right; break; + case AxisDirection::DownRight: key=KeyDirection::Right; break; + case AxisDirection::UpLeft: key=KeyDirection::Left; break; + case AxisDirection::UpRight: key=KeyDirection::Left; break; + } + break; + case KeyDirection::Left: + switch(axisDirection) + { + case AxisDirection::LeftDown: key=KeyDirection::Right; break; + case AxisDirection::RightDown: key=KeyDirection::Left; break; + case AxisDirection::LeftUp: key=KeyDirection::Right; break; + case AxisDirection::RightUp: key=KeyDirection::Left; break; + case AxisDirection::DownLeft: key=KeyDirection::Down; break; + case AxisDirection::DownRight: key=KeyDirection::Up; break; + case AxisDirection::UpLeft: key=KeyDirection::Down; break; + case AxisDirection::UpRight: key=KeyDirection::Up; break; + } + break; + case KeyDirection::Right: + switch(axisDirection) + { + case AxisDirection::LeftDown: key=KeyDirection::Left; break; + case AxisDirection::RightDown: key=KeyDirection::Right; break; + case AxisDirection::LeftUp: key=KeyDirection::Left; break; + case AxisDirection::RightUp: key=KeyDirection::Right; break; + case AxisDirection::DownLeft: key=KeyDirection::Up; break; + case AxisDirection::DownRight: key=KeyDirection::Down; break; + case AxisDirection::UpLeft: key=KeyDirection::Up; break; + case AxisDirection::UpRight: key=KeyDirection::Down; break; + } + break; + case KeyDirection::Home: + switch(axisDirection) + { + case AxisDirection::LeftDown: key=KeyDirection::Home; break; + case AxisDirection::RightDown: key=KeyDirection::Home; break; + case AxisDirection::LeftUp: key=KeyDirection::End; break; + case AxisDirection::RightUp: key=KeyDirection::End; break; + case AxisDirection::DownLeft: key=KeyDirection::Home; break; + case AxisDirection::DownRight: key=KeyDirection::Home; break; + case AxisDirection::UpLeft: key=KeyDirection::End; break; + case AxisDirection::UpRight: key=KeyDirection::End; break; + } + break; + case KeyDirection::End: + switch(axisDirection) + { + case AxisDirection::LeftDown: key=KeyDirection::End; break; + case AxisDirection::RightDown: key=KeyDirection::End; break; + case AxisDirection::LeftUp: key=KeyDirection::Home; break; + case AxisDirection::RightUp: key=KeyDirection::Home; break; + case AxisDirection::DownLeft: key=KeyDirection::End; break; + case AxisDirection::DownRight: key=KeyDirection::End; break; + case AxisDirection::UpLeft: key=KeyDirection::Home; break; + case AxisDirection::UpRight: key=KeyDirection::Home; break; + } + break; + default:; + } + + if(pageKey) + { + switch(key) + { + case KeyDirection::Up: + key=KeyDirection::PageUp; + break; + case KeyDirection::Down: + key=KeyDirection::PageDown; + break; + case KeyDirection::Left: + key=KeyDirection::PageLeft; + break; + case KeyDirection::Right: + key=KeyDirection::PageRight; + break; + default:; + } + } + return key; + } + } + } +} + +/*********************************************************************** +.\GRAPHICSCOMPOSITION\GUIGRAPHICSBASICCOMPOSITION.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + using namespace collections; + using namespace controls; + using namespace elements; + +/*********************************************************************** +GuiWindowComposition +***********************************************************************/ + + GuiWindowComposition::GuiWindowComposition() + { + } + + GuiWindowComposition::~GuiWindowComposition() + { + } + + Rect GuiWindowComposition::GetBounds() + { + Rect bounds; + if (relatedHostRecord) + { + if (auto window = relatedHostRecord->host->GetNativeWindow()) + { + bounds = Rect(Point(0, 0), window->GetClientSize()); + } + } + UpdatePreviousBounds(bounds); + return bounds; + } + + void GuiWindowComposition::SetMargin(Margin value) + { + } + +/*********************************************************************** +GuiBoundsComposition +***********************************************************************/ + + GuiBoundsComposition::GuiBoundsComposition() + { + } + + GuiBoundsComposition::~GuiBoundsComposition() + { + } + + bool GuiBoundsComposition::GetSizeAffectParent() + { + return sizeAffectParent; + } + + void GuiBoundsComposition::SetSizeAffectParent(bool value) + { + sizeAffectParent = value; + } + + bool GuiBoundsComposition::IsSizeAffectParent() + { + return sizeAffectParent; + } + + Rect GuiBoundsComposition::GetPreferredBounds() + { + Rect result = GetBoundsInternal(compositionBounds); + if (GetParent() && IsAlignedToParent()) + { + if (alignmentToParent.left >= 0) + { + vint offset = alignmentToParent.left - result.x1; + result.x1 += offset; + result.x2 += offset; + } + if (alignmentToParent.top >= 0) + { + vint offset = alignmentToParent.top - result.y1; + result.y1 += offset; + result.y2 += offset; + } + if (alignmentToParent.right >= 0) + { + result.x2 += alignmentToParent.right; + } + if (alignmentToParent.bottom >= 0) + { + result.y2 += alignmentToParent.bottom; + } + } + return result; + } + + Rect GuiBoundsComposition::GetBounds() + { + Rect result = GetPreferredBounds(); + if (GetParent() && IsAlignedToParent()) + { + Size clientSize = GetParent()->GetClientArea().GetSize(); + if (alignmentToParent.left >= 0 && alignmentToParent.right >= 0) + { + result.x1 = alignmentToParent.left; + result.x2 = clientSize.x - alignmentToParent.right; + } + else if (alignmentToParent.left >= 0) + { + vint width = result.Width(); + result.x1 = alignmentToParent.left; + result.x2 = result.x1 + width; + } + else if (alignmentToParent.right >= 0) + { + vint width = result.Width(); + result.x2 = clientSize.x - alignmentToParent.right; + result.x1 = result.x2 - width; + } + + if (alignmentToParent.top >= 0 && alignmentToParent.bottom >= 0) + { + result.y1 = alignmentToParent.top; + result.y2 = clientSize.y - alignmentToParent.bottom; + } + else if (alignmentToParent.top >= 0) + { + vint height = result.Height(); + result.y1 = alignmentToParent.top; + result.y2 = result.y1 + height; + } + else if (alignmentToParent.bottom >= 0) + { + vint height = result.Height(); + result.y2 = clientSize.y - alignmentToParent.bottom; + result.y1 = result.y2 - height; + } + } + UpdatePreviousBounds(result); + return result; + } + + void GuiBoundsComposition::SetBounds(Rect value) + { + compositionBounds = value; + InvokeOnCompositionStateChanged(); + } + + Margin GuiBoundsComposition::GetAlignmentToParent() + { + return alignmentToParent; + } + + void GuiBoundsComposition::SetAlignmentToParent(Margin value) + { + alignmentToParent = value; + InvokeOnCompositionStateChanged(); + } + + bool GuiBoundsComposition::IsAlignedToParent() + { + return alignmentToParent != Margin(-1, -1, -1, -1); + } + } + } +} + +/*********************************************************************** +.\GRAPHICSCOMPOSITION\GUIGRAPHICSCOMPOSITION.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + using namespace reflection::description; + using namespace collections; + using namespace controls; + using namespace elements; + +/*********************************************************************** +GuiSharedSizeItemComposition +***********************************************************************/ + + void GuiSharedSizeItemComposition::Update() + { + if (parentRoot) + { + parentRoot->ForceCalculateSizeImmediately(); + } + InvokeOnCompositionStateChanged(); + } + + void GuiSharedSizeItemComposition::OnParentLineChanged() + { + GuiBoundsComposition::OnParentLineChanged(); + if (parentRoot) + { + parentRoot->childItems.Remove(this); + parentRoot = nullptr; + } + + auto current = GetParent(); + while (current) + { + if (auto item = dynamic_cast(current)) + { + break; + } + else if (auto root = dynamic_cast(current)) + { + parentRoot = root; + break; + } + current = current->GetParent(); + } + + if (parentRoot) + { + parentRoot->childItems.Add(this); + Size minSize; + if (sharedWidth) + { + vint index = parentRoot->itemWidths.Keys().IndexOf(group); + if (index != -1) + { + minSize.x = parentRoot->itemWidths.Values()[index]; + } + } + if (sharedHeight) + { + vint index = parentRoot->itemHeights.Keys().IndexOf(group); + if (index != -1) + { + minSize.y = parentRoot->itemHeights.Values()[index]; + } + } + SetPreferredMinSize(minSize); + } + } + + GuiSharedSizeItemComposition::GuiSharedSizeItemComposition() + { + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + } + + GuiSharedSizeItemComposition::~GuiSharedSizeItemComposition() + { + } + + const WString& GuiSharedSizeItemComposition::GetGroup() + { + return group; + } + + void GuiSharedSizeItemComposition::SetGroup(const WString& value) + { + if (group != value) + { + group = value; + Update(); + } + } + + bool GuiSharedSizeItemComposition::GetSharedWidth() + { + return sharedWidth; + } + + void GuiSharedSizeItemComposition::SetSharedWidth(bool value) + { + if (sharedWidth != value) + { + sharedWidth = value; + Update(); + } + } + + bool GuiSharedSizeItemComposition::GetSharedHeight() + { + return sharedHeight; + } + + void GuiSharedSizeItemComposition::SetSharedHeight(bool value) + { + if (sharedHeight != value) + { + sharedHeight = value; + Update(); + } + } + +/*********************************************************************** +GuiSharedSizeRootComposition +***********************************************************************/ + + void GuiSharedSizeRootComposition::AddSizeComponent(collections::Dictionary& sizes, const WString& group, vint sizeComponent) + { + vint index = sizes.Keys().IndexOf(group); + if (index == -1) + { + sizes.Add(group, sizeComponent); + } + else if (sizes.Values().Get(index) < sizeComponent) + { + sizes.Set(group, sizeComponent); + } + } + + void GuiSharedSizeRootComposition::CollectSizes(collections::Dictionary& widths, collections::Dictionary& heights) + { + FOREACH(GuiSharedSizeItemComposition*, item, childItems) + { + auto group = item->GetGroup(); + auto minSize = item->GetPreferredMinSize(); + item->SetPreferredMinSize(Size(0, 0)); + auto size = item->GetPreferredBounds().GetSize(); + + if (item->GetSharedWidth()) + { + AddSizeComponent(widths, group, size.x); + } + if (item->GetSharedHeight()) + { + AddSizeComponent(heights, group, size.y); + } + + item->SetPreferredMinSize(minSize); + } + } + + void GuiSharedSizeRootComposition::AlignSizes(collections::Dictionary& widths, collections::Dictionary& heights) + { + FOREACH(GuiSharedSizeItemComposition*, item, childItems) + { + auto group = item->GetGroup(); + auto size = item->GetPreferredMinSize(); + + if (item->GetSharedWidth()) + { + size.x = widths[group]; + } + if (item->GetSharedHeight()) + { + size.y = heights[group]; + } + + item->SetPreferredMinSize(size); + } + } + + GuiSharedSizeRootComposition::GuiSharedSizeRootComposition() + { + } + + GuiSharedSizeRootComposition::~GuiSharedSizeRootComposition() + { + } + + void GuiSharedSizeRootComposition::ForceCalculateSizeImmediately() + { + itemWidths.Clear(); + itemHeights.Clear(); + + CollectSizes(itemWidths, itemHeights); + AlignSizes(itemWidths, itemHeights); + GuiBoundsComposition::ForceCalculateSizeImmediately(); + } + + Rect GuiSharedSizeRootComposition::GetBounds() + { + Dictionary widths, heights; + CollectSizes(widths, heights); + bool minSizeModified = CompareEnumerable(itemWidths, widths) != 0 || CompareEnumerable(itemHeights, heights) != 0; + + if (minSizeModified) + { + CopyFrom(itemWidths, widths); + CopyFrom(itemHeights, heights); + AlignSizes(itemWidths, itemHeights); + GuiBoundsComposition::ForceCalculateSizeImmediately(); + } + return GuiBoundsComposition::GetBounds(); + } + +/*********************************************************************** +GuiRepeatCompositionBase +***********************************************************************/ + + void GuiRepeatCompositionBase::OnItemChanged(vint index, vint oldCount, vint newCount) + { + if (itemTemplate && itemSource) + { + for (vint i = oldCount - 1; i >= 0; i--) + { + RemoveItem(index + i); + } + + for (vint i = 0; i < newCount; i++) + { + InstallItem(index + i); + } + } + } + + void GuiRepeatCompositionBase::RemoveItem(vint index) + { + GuiItemEventArgs arguments(dynamic_cast(this)); + arguments.itemIndex = index; + ItemRemoved.Execute(arguments); + + auto item = RemoveRepeatComposition(index); + SafeDeleteComposition(item); + } + + void GuiRepeatCompositionBase::InstallItem(vint index) + { + auto source = itemSource->Get(index); + auto templateItem = itemTemplate(source); + auto item = InsertRepeatComposition(index); + + templateItem->SetAlignmentToParent(Margin(0, 0, 0, 0)); + item->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + item->AddChild(templateItem); + + GuiItemEventArgs arguments(dynamic_cast(this)); + arguments.itemIndex = index; + ItemInserted.Execute(arguments); + } + + void GuiRepeatCompositionBase::ClearItems() + { + for (vint i = GetRepeatCompositionCount() - 1; i >= 0; i--) + { + RemoveItem(i); + } + } + + void GuiRepeatCompositionBase::InstallItems() + { + if (itemTemplate && itemSource) + { + vint count = itemSource->GetCount(); + for (vint i = 0; i < count; i++) + { + InstallItem(i); + } + } + } + + GuiRepeatCompositionBase::GuiRepeatCompositionBase() + { + } + + GuiRepeatCompositionBase::~GuiRepeatCompositionBase() + { + } + + GuiRepeatCompositionBase::ItemStyleProperty GuiRepeatCompositionBase::GetItemTemplate() + { + return itemTemplate; + } + + void GuiRepeatCompositionBase::SetItemTemplate(ItemStyleProperty value) + { + ClearItems(); + itemTemplate = value; + if (itemTemplate && itemSource) + { + InstallItems(); + } + } + + Ptr GuiRepeatCompositionBase::GetItemSource() + { + return itemSource; + } + + void GuiRepeatCompositionBase::SetItemSource(Ptr value) + { + if (value != itemSource) + { + if (itemChangedHandler) + { + itemSource.Cast()->ItemChanged.Remove(itemChangedHandler); + } + + ClearItems(); + itemSource = value.Cast(); + if (!itemSource && value) + { + itemSource = IValueList::Create(GetLazyList(value)); + } + + if (itemTemplate && itemSource) + { + InstallItems(); + } + if (auto observable = itemSource.Cast()) + { + itemChangedHandler = observable->ItemChanged.Add(this, &GuiRepeatCompositionBase::OnItemChanged); + } + } + } + +/*********************************************************************** +GuiRepeatStackComposition +***********************************************************************/ + + vint GuiRepeatStackComposition::GetRepeatCompositionCount() + { + return stackItems.Count(); + } + + GuiGraphicsComposition* GuiRepeatStackComposition::GetRepeatComposition(vint index) + { + return stackItems[index]; + } + + GuiGraphicsComposition* GuiRepeatStackComposition::InsertRepeatComposition(vint index) + { + CHECK_ERROR(0 <= index && index <= stackItems.Count(), L"GuiRepeatStackComposition::InsertRepeatComposition(vint)#Index out of range."); + auto item = new GuiStackItemComposition; + InsertStackItem(index, item); + return item; + } + + GuiGraphicsComposition* GuiRepeatStackComposition::RemoveRepeatComposition(vint index) + { + auto item = stackItems[index]; + RemoveChild(item); + return item; + } + +/*********************************************************************** +GuiRepeatFlowComposition +***********************************************************************/ + + vint GuiRepeatFlowComposition::GetRepeatCompositionCount() + { + return flowItems.Count(); + } + + GuiGraphicsComposition* GuiRepeatFlowComposition::GetRepeatComposition(vint index) + { + return flowItems[index]; + } + + GuiGraphicsComposition* GuiRepeatFlowComposition::InsertRepeatComposition(vint index) + { + CHECK_ERROR(0 <= index && index <= flowItems.Count(), L"GuiRepeatStackComposition::InsertRepeatComposition(vint)#Index out of range."); + auto item = new GuiFlowItemComposition; + InsertFlowItem(index, item); + return item; + } + + GuiGraphicsComposition* GuiRepeatFlowComposition::RemoveRepeatComposition(vint index) + { + auto item = flowItems[index]; + RemoveChild(item); + return item; + } + } + } +} + +/*********************************************************************** +.\GRAPHICSCOMPOSITION\GUIGRAPHICSCOMPOSITIONBASE.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + using namespace collections; + using namespace controls; + using namespace elements; + + void InvokeOnCompositionStateChanged(compositions::GuiGraphicsComposition* composition) + { + composition->InvokeOnCompositionStateChanged(); + } + +/*********************************************************************** +GuiGraphicsComposition +***********************************************************************/ + + void GuiGraphicsComposition::OnControlParentChanged(controls::GuiControl* control) + { + if(associatedControl && associatedControl!=control) + { + if(associatedControl->GetParent()) + { + associatedControl->GetParent()->OnChildRemoved(associatedControl); + } + if(control) + { + control->OnChildInserted(associatedControl); + } + } + else + { + for(vint i=0;iOnControlParentChanged(control); + } + } + } + + void GuiGraphicsComposition::OnChildInserted(GuiGraphicsComposition* child) + { + child->OnControlParentChanged(GetRelatedControl()); + } + + void GuiGraphicsComposition::OnChildRemoved(GuiGraphicsComposition* child) + { + child->OnControlParentChanged(0); + } + + void GuiGraphicsComposition::OnParentChanged(GuiGraphicsComposition* oldParent, GuiGraphicsComposition* newParent) + { + OnParentLineChanged(); + } + + void GuiGraphicsComposition::OnParentLineChanged() + { + for (vint i = 0; i < children.Count(); i++) + { + children[i]->OnParentLineChanged(); + } + } + + void GuiGraphicsComposition::OnRenderContextChanged() + { + } + + void GuiGraphicsComposition::UpdateRelatedHostRecord(GraphicsHostRecord* record) + { + relatedHostRecord = record; + auto renderTarget = GetRenderTarget(); + + if (ownedElement) + { + if (auto renderer = ownedElement->GetRenderer()) + { + renderer->SetRenderTarget(renderTarget); + } + } + + for (vint i = 0; i < children.Count(); i++) + { + children[i]->UpdateRelatedHostRecord(record); + } + + if (HasEventReceiver()) + { + GetEventReceiver()->renderTargetChanged.Execute(GuiEventArgs(this)); + } + if (associatedControl) + { + associatedControl->OnRenderTargetChanged(renderTarget); + } + + OnRenderContextChanged(); + } + + void GuiGraphicsComposition::SetAssociatedControl(controls::GuiControl* control) + { + if (associatedControl) + { + for (vint i = 0; i < children.Count(); i++) + { + children[i]->OnControlParentChanged(0); + } + } + associatedControl = control; + if (associatedControl) + { + for (vint i = 0; i < children.Count(); i++) + { + children[i]->OnControlParentChanged(associatedControl); + } + } + } + + void GuiGraphicsComposition::InvokeOnCompositionStateChanged() + { + if (relatedHostRecord) + { + relatedHostRecord->host->RequestRender(); + } + } + + bool GuiGraphicsComposition::SharedPtrDestructorProc(DescriptableObject* obj, bool forceDisposing) + { + GuiGraphicsComposition* value=dynamic_cast(obj); + if(value->parent) + { + if (!forceDisposing) return false; + } + SafeDeleteComposition(value); + return true; + } + + GuiGraphicsComposition::GuiGraphicsComposition() + { + sharedPtrDestructorProc = &GuiGraphicsComposition::SharedPtrDestructorProc; + } + + GuiGraphicsComposition::~GuiGraphicsComposition() + { + for(vint i=0;iGetParent()) return false; + children.Insert(index, child); + + // composition parent changed -> control parent changed -> related host changed + child->parent = this; + child->OnParentChanged(nullptr, this); + OnChildInserted(child); + child->UpdateRelatedHostRecord(relatedHostRecord); + + InvokeOnCompositionStateChanged(); + return true; + } + + bool GuiGraphicsComposition::RemoveChild(GuiGraphicsComposition* child) + { + CHECK_ERROR(!isRendering, L"GuiGraphicsComposition::InsertChild(vint, GuiGraphicsComposition*)#Cannot modify composition tree during rendering."); + if (!child) return false; + vint index = children.IndexOf(child); + if (index == -1) return false; + + // composition parent changed -> control parent changed -> related host changed + child->parent = nullptr; + child->OnParentChanged(this, nullptr); + OnChildRemoved(child); + child->UpdateRelatedHostRecord(nullptr); + + GuiGraphicsHost* host = GetRelatedGraphicsHost(); + if (host) + { + host->DisconnectComposition(child); + } + children.RemoveAt(index); + InvokeOnCompositionStateChanged(); + return true; + } + + bool GuiGraphicsComposition::MoveChild(GuiGraphicsComposition* child, vint newIndex) + { + if(!child) return false; + vint index=children.IndexOf(child); + if(index==-1) return false; + children.RemoveAt(index); + children.Insert(newIndex, child); + InvokeOnCompositionStateChanged(); + return true; + } + + Ptr GuiGraphicsComposition::GetOwnedElement() + { + return ownedElement; + } + + void GuiGraphicsComposition::SetOwnedElement(Ptr element) + { + if (ownedElement != element) + { + if (ownedElement) + { + if (auto renderer = ownedElement->GetRenderer()) + { + renderer->SetRenderTarget(nullptr); + } + ownedElement->SetOwnerComposition(nullptr); + } + ownedElement = element; + if (ownedElement) + { + if (auto renderer = ownedElement->GetRenderer()) + { + renderer->SetRenderTarget(GetRenderTarget()); + } + ownedElement->SetOwnerComposition(this); + } + InvokeOnCompositionStateChanged(); + } + } + + bool GuiGraphicsComposition::GetVisible() + { + return visible; + } + + void GuiGraphicsComposition::SetVisible(bool value) + { + visible = value; + InvokeOnCompositionStateChanged(); + } + + GuiGraphicsComposition::MinSizeLimitation GuiGraphicsComposition::GetMinSizeLimitation() + { + return minSizeLimitation; + } + + void GuiGraphicsComposition::SetMinSizeLimitation(MinSizeLimitation value) + { + minSizeLimitation = value; + InvokeOnCompositionStateChanged(); + } + + elements::IGuiGraphicsRenderTarget* GuiGraphicsComposition::GetRenderTarget() + { + return relatedHostRecord ? relatedHostRecord->renderTarget : nullptr; + } + + void GuiGraphicsComposition::Render(Size offset) + { + auto renderTarget = GetRenderTarget(); + if (visible && renderTarget && !renderTarget->IsClipperCoverWholeTarget()) + { + Rect bounds = GetBounds(); + bounds.x1 += margin.left; + bounds.y1 += margin.top; + bounds.x2 -= margin.right; + bounds.y2 -= margin.bottom; + + if (bounds.x1 <= bounds.x2 && bounds.y1 <= bounds.y2) + { + bounds.x1 += offset.x; + bounds.x2 += offset.x; + bounds.y1 += offset.y; + bounds.y2 += offset.y; + + isRendering = true; + if (ownedElement) + { + IGuiGraphicsRenderer* renderer = ownedElement->GetRenderer(); + if (renderer) + { + renderer->Render(bounds); + } + } + if (children.Count() > 0) + { + bounds.x1 += internalMargin.left; + bounds.y1 += internalMargin.top; + bounds.x2 -= internalMargin.right; + bounds.y2 -= internalMargin.bottom; + if (bounds.x1 <= bounds.x2 && bounds.y1 <= bounds.y2) + { + offset = bounds.GetSize(); + renderTarget->PushClipper(bounds); + if (!renderTarget->IsClipperCoverWholeTarget()) + { + for (vint i = 0; i < children.Count(); i++) + { + children[i]->Render(Size(bounds.x1, bounds.y1)); + } + } + renderTarget->PopClipper(); + } + } + isRendering = false; + } + } + } + + GuiGraphicsEventReceiver* GuiGraphicsComposition::GetEventReceiver() + { + if(!eventReceiver) + { + eventReceiver=new GuiGraphicsEventReceiver(this); + } + return eventReceiver.Obj(); + } + + bool GuiGraphicsComposition::HasEventReceiver() + { + return eventReceiver; + } + + GuiGraphicsComposition* GuiGraphicsComposition::FindComposition(Point location, bool forMouseEvent) + { + if (!visible) return 0; + Rect bounds = GetBounds(); + Rect relativeBounds = Rect(Point(0, 0), bounds.GetSize()); + if (relativeBounds.Contains(location)) + { + Rect clientArea = GetClientArea(); + for (vint i = children.Count() - 1; i >= 0; i--) + { + GuiGraphicsComposition* child = children[i]; + Rect childBounds = child->GetBounds(); + vint offsetX = childBounds.x1 + (clientArea.x1 - bounds.x1); + vint offsetY = childBounds.y1 + (clientArea.y1 - bounds.y1); + Point newLocation = location - Size(offsetX, offsetY); + GuiGraphicsComposition* childResult = child->FindComposition(newLocation, forMouseEvent); + if (childResult) + { + return childResult; + } + } + + if (!forMouseEvent || !transparentToMouse) + { + return this; + } + } + return nullptr; + } + + bool GuiGraphicsComposition::GetTransparentToMouse() + { + return transparentToMouse; + } + + void GuiGraphicsComposition::SetTransparentToMouse(bool value) + { + transparentToMouse = value; + } + + Rect GuiGraphicsComposition::GetGlobalBounds() + { + Rect bounds = GetBounds(); + GuiGraphicsComposition* composition = parent; + while (composition) + { + Rect clientArea = composition->GetClientArea(); + Rect parentBounds = composition->GetBounds(); + bounds.x1 += clientArea.x1; + bounds.x2 += clientArea.x1; + bounds.y1 += clientArea.y1; + bounds.y2 += clientArea.y1; + composition = composition->parent; + } + return bounds; + } + + controls::GuiControl* GuiGraphicsComposition::GetAssociatedControl() + { + return associatedControl; + } + + GuiGraphicsHost* GuiGraphicsComposition::GetAssociatedHost() + { + if (relatedHostRecord && relatedHostRecord->host->GetMainComposition() == this) + { + return relatedHostRecord->host; + } + else + { + return nullptr; + } + } + + INativeCursor* GuiGraphicsComposition::GetAssociatedCursor() + { + return associatedCursor; + } + + void GuiGraphicsComposition::SetAssociatedCursor(INativeCursor* cursor) + { + associatedCursor = cursor; + } + + INativeWindowListener::HitTestResult GuiGraphicsComposition::GetAssociatedHitTestResult() + { + return associatedHitTestResult; + } + + void GuiGraphicsComposition::SetAssociatedHitTestResult(INativeWindowListener::HitTestResult value) + { + associatedHitTestResult = value; + } + + controls::GuiControl* GuiGraphicsComposition::GetRelatedControl() + { + GuiGraphicsComposition* composition = this; + while (composition) + { + if (composition->GetAssociatedControl()) + { + return composition->GetAssociatedControl(); + } + else + { + composition = composition->GetParent(); + } + } + return nullptr; + } + + GuiGraphicsHost* GuiGraphicsComposition::GetRelatedGraphicsHost() + { + return relatedHostRecord ? relatedHostRecord->host : nullptr; + } + + controls::GuiControlHost* GuiGraphicsComposition::GetRelatedControlHost() + { + if (auto control = GetRelatedControl()) + { + return control->GetRelatedControlHost(); + } + return nullptr; + } + + INativeCursor* GuiGraphicsComposition::GetRelatedCursor() + { + GuiGraphicsComposition* composition = this; + while (composition) + { + if (composition->GetAssociatedCursor()) + { + return composition->GetAssociatedCursor(); + } + else + { + composition = composition->GetParent(); + } + } + return nullptr; + } + + Margin GuiGraphicsComposition::GetMargin() + { + return margin; + } + + void GuiGraphicsComposition::SetMargin(Margin value) + { + margin = value; + InvokeOnCompositionStateChanged(); + } + + Margin GuiGraphicsComposition::GetInternalMargin() + { + return internalMargin; + } + + void GuiGraphicsComposition::SetInternalMargin(Margin value) + { + internalMargin = value; + InvokeOnCompositionStateChanged(); + } + + Size GuiGraphicsComposition::GetPreferredMinSize() + { + return preferredMinSize; + } + + void GuiGraphicsComposition::SetPreferredMinSize(Size value) + { + preferredMinSize = value; + InvokeOnCompositionStateChanged(); + } + + Rect GuiGraphicsComposition::GetClientArea() + { + Rect bounds=GetBounds(); + bounds.x1+=margin.left+internalMargin.left; + bounds.y1+=margin.top+internalMargin.top; + bounds.x2-=margin.right+internalMargin.right; + bounds.y2-=margin.bottom+internalMargin.bottom; + return bounds; + } + + void GuiGraphicsComposition::ForceCalculateSizeImmediately() + { + isRendering = true; + for (vint i = 0; i < children.Count(); i++) + { + children[i]->ForceCalculateSizeImmediately(); + } + isRendering = false; + InvokeOnCompositionStateChanged(); + } + +/*********************************************************************** +GuiGraphicsSite +***********************************************************************/ + + Rect GuiGraphicsSite::GetBoundsInternal(Rect expectedBounds) + { + Size minSize = GetMinPreferredClientSize(); + if (minSize.x < preferredMinSize.x) minSize.x = preferredMinSize.x; + if (minSize.y < preferredMinSize.y) minSize.y = preferredMinSize.y; + + minSize.x += margin.left + margin.right + internalMargin.left + internalMargin.right; + minSize.y += margin.top + margin.bottom + internalMargin.top + internalMargin.bottom; + vint w = expectedBounds.Width(); + vint h = expectedBounds.Height(); + if (minSize.x < w) minSize.x = w; + if (minSize.y < h) minSize.y = h; + return Rect(expectedBounds.LeftTop(), minSize); + } + + void GuiGraphicsSite::UpdatePreviousBounds(Rect bounds) + { + if (previousBounds != bounds) + { + previousBounds = bounds; + BoundsChanged.Execute(GuiEventArgs(this)); + InvokeOnCompositionStateChanged(); + } + } + + GuiGraphicsSite::GuiGraphicsSite() + { + BoundsChanged.SetAssociatedComposition(this); + } + + GuiGraphicsSite::~GuiGraphicsSite() + { + } + + bool GuiGraphicsSite::IsSizeAffectParent() + { + return true; + } + + Size GuiGraphicsSite::GetMinPreferredClientSize() + { + Size minSize; + if (minSizeLimitation != GuiGraphicsComposition::NoLimit) + { + if (ownedElement) + { + IGuiGraphicsRenderer* renderer = ownedElement->GetRenderer(); + if (renderer) + { + minSize = renderer->GetMinSize(); + } + } + } + if (minSizeLimitation == GuiGraphicsComposition::LimitToElementAndChildren) + { + vint childCount = Children().Count(); + for (vint i = 0; i < childCount; i++) + { + GuiGraphicsComposition* child = children[i]; + if (child->IsSizeAffectParent()) + { + Rect childBounds = child->GetPreferredBounds(); + if (minSize.x < childBounds.x2) minSize.x = childBounds.x2; + if (minSize.y < childBounds.y2) minSize.y = childBounds.y2; + } + } + } + return minSize; + } + + Rect GuiGraphicsSite::GetPreferredBounds() + { + return GetBoundsInternal(Rect(Point(0, 0), GetMinPreferredClientSize())); + } + +/*********************************************************************** +Helper Functions +***********************************************************************/ + + void NotifyFinalizeInstance(controls::GuiControl* value) + { + if (value) + { + NotifyFinalizeInstance(value->GetBoundsComposition()); + } + } + + void NotifyFinalizeInstance(GuiGraphicsComposition* value) + { + if (value) + { + bool finalized = false; + if (auto root = dynamic_cast(value)) + { + if (root->IsFinalized()) + { + finalized = true; + } + else + { + root->FinalizeInstance(); + } + } + + if (auto control = value->GetAssociatedControl()) + { + if (auto root = dynamic_cast(control)) + { + if (root->IsFinalized()) + { + finalized = true; + } + else + { + root->FinalizeInstance(); + } + } + } + + if (!finalized) + { + vint count = value->Children().Count(); + for (vint i = 0; i < count; i++) + { + NotifyFinalizeInstance(value->Children()[i]); + } + } + } + } + + void SafeDeleteControlInternal(controls::GuiControl* value) + { + if(value) + { + if (value->GetRelatedControlHost() != value) + { + GuiGraphicsComposition* bounds = value->GetBoundsComposition(); + if (bounds->GetParent()) + { + bounds->GetParent()->RemoveChild(bounds); + } + } + delete value; + } + } + + void SafeDeleteCompositionInternal(GuiGraphicsComposition* value) + { + if (value) + { + if (value->GetParent()) + { + value->GetParent()->RemoveChild(value); + } + + if (value->GetAssociatedControl()) + { + SafeDeleteControlInternal(value->GetAssociatedControl()); + } + else + { + for (vint i = value->Children().Count() - 1; i >= 0; i--) + { + SafeDeleteCompositionInternal(value->Children().Get(i)); + } + delete value; + } + } + } + + void SafeDeleteControl(controls::GuiControl* value) + { + if (auto controlHost = dynamic_cast(value)) + { + controlHost->DeleteAfterProcessingAllEvents(); + } + else + { + NotifyFinalizeInstance(value); + SafeDeleteControlInternal(value); + } + } + + void SafeDeleteComposition(GuiGraphicsComposition* value) + { + NotifyFinalizeInstance(value); + SafeDeleteCompositionInternal(value); + } + } + } +} + +/*********************************************************************** +.\GRAPHICSCOMPOSITION\GUIGRAPHICSEVENTRECEIVER.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + +/*********************************************************************** +Event Receiver +***********************************************************************/ + + GuiGraphicsEventReceiver::GuiGraphicsEventReceiver(GuiGraphicsComposition* _sender) + :sender(_sender) + ,leftButtonDown(_sender) + ,leftButtonUp(_sender) + ,leftButtonDoubleClick(_sender) + ,middleButtonDown(_sender) + ,middleButtonUp(_sender) + ,middleButtonDoubleClick(_sender) + ,rightButtonDown(_sender) + ,rightButtonUp(_sender) + ,rightButtonDoubleClick(_sender) + ,horizontalWheel(_sender) + ,verticalWheel(_sender) + ,mouseMove(_sender) + ,mouseEnter(_sender) + ,mouseLeave(_sender) + ,previewKey(_sender) + ,keyDown(_sender) + ,keyUp(_sender) + ,systemKeyDown(_sender) + ,systemKeyUp(_sender) + ,previewCharInput(_sender) + ,charInput(_sender) + ,gotFocus(_sender) + ,lostFocus(_sender) + ,caretNotify(_sender) + ,clipboardNotify(_sender) + { + } + + GuiGraphicsEventReceiver::~GuiGraphicsEventReceiver() + { + } + + GuiGraphicsComposition* GuiGraphicsEventReceiver::GetAssociatedComposition() + { + return sender; + } + } + } +} + +/*********************************************************************** +.\GRAPHICSCOMPOSITION\GUIGRAPHICSFLOWCOMPOSITION.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + using namespace collections; + +/*********************************************************************** +GuiFlowComposition +***********************************************************************/ + + void GuiFlowComposition::UpdateFlowItemBounds(bool forceUpdate) + { + if (forceUpdate || needUpdate) + { + needUpdate = false; + InvokeOnCompositionStateChanged(); + + auto clientMargin = axis->RealMarginToVirtualMargin(extraMargin); + if (clientMargin.left < 0) clientMargin.left = 0; + if (clientMargin.top < 0) clientMargin.top = 0; + if (clientMargin.right < 0) clientMargin.right = 0; + if (clientMargin.bottom < 0) clientMargin.bottom = 0; + + auto realFullSize = previousBounds.GetSize(); + auto clientSize = axis->RealSizeToVirtualSize(realFullSize); + clientSize.x -= (clientMargin.left + clientMargin.right); + clientSize.y -= (clientMargin.top + clientMargin.bottom); + + flowItemBounds.Resize(flowItems.Count()); + for (vint i = 0; i < flowItems.Count(); i++) + { + flowItemBounds[i] = Rect(Point(0, 0), flowItems[i]->GetMinSize()); + } + + vint currentIndex = 0; + vint rowTop = 0; + + while (currentIndex < flowItems.Count()) + { + auto itemSize = axis->RealSizeToVirtualSize(flowItemBounds[currentIndex].GetSize()); + vint rowWidth = itemSize.x; + vint rowHeight = itemSize.y; + vint rowItemCount = 1; + + for (vint i = currentIndex + 1; i < flowItems.Count(); i++) + { + itemSize = axis->RealSizeToVirtualSize(flowItemBounds[i].GetSize()); + vint itemWidth = itemSize.x + columnPadding; + if (rowWidth + itemWidth > clientSize.x) + { + break; + } + rowWidth += itemWidth; + if (rowHeight < itemSize.y) + { + rowHeight = itemSize.y; + } + rowItemCount++; + } + + vint baseLine = 0; + Array itemBaseLines(rowItemCount); + for (vint i = 0; i < rowItemCount; i++) + { + vint index = currentIndex + i; + vint itemBaseLine = 0; + itemSize = axis->RealSizeToVirtualSize(flowItemBounds[index].GetSize()); + + auto option = flowItems[index]->GetFlowOption(); + switch (option.baseline) + { + case GuiFlowOption::FromTop: + itemBaseLine = option.distance; + break; + case GuiFlowOption::FromBottom: + itemBaseLine = itemSize.y - option.distance; + break; + case GuiFlowOption::Percentage: + itemBaseLine = (vint)(itemSize.y*option.percentage); + break; + } + + itemBaseLines[i] = itemBaseLine; + if (baseLine < itemBaseLine) + { + baseLine = itemBaseLine; + } + } + + vint rowUsedWidth = 0; + for (vint i = 0; i < rowItemCount; i++) + { + vint index = currentIndex + i; + itemSize = axis->RealSizeToVirtualSize(flowItemBounds[index].GetSize()); + + vint itemLeft = 0; + vint itemTop = rowTop + baseLine - itemBaseLines[i]; + + switch (alignment) + { + case FlowAlignment::Left: + itemLeft = rowUsedWidth + i * columnPadding; + break; + case FlowAlignment::Center: + itemLeft = rowUsedWidth + i * columnPadding + (clientSize.x - rowWidth) / 2; + break; + case FlowAlignment::Extend: + if (i == 0) + { + itemLeft = rowUsedWidth; + } + else + { + itemLeft = rowUsedWidth + (vint)((double)(clientSize.x - rowWidth) * i / (rowItemCount - 1)) + i * columnPadding; + } + break; + } + + flowItemBounds[index] = axis->VirtualRectToRealRect( + realFullSize, + Rect( + Point( + itemLeft + clientMargin.left, + itemTop + clientMargin.top + ), + itemSize + ) + ); + rowUsedWidth += itemSize.x; + } + + rowTop += rowHeight + rowPadding; + currentIndex += rowItemCount; + } + + minHeight = rowTop == 0 ? 0 : rowTop - rowPadding; + } + } + + void GuiFlowComposition::OnBoundsChanged(GuiGraphicsComposition* sender, GuiEventArgs& arguments) + { + UpdateFlowItemBounds(true); + } + + void GuiFlowComposition::OnChildInserted(GuiGraphicsComposition* child) + { + GuiBoundsComposition::OnChildInserted(child); + auto item = dynamic_cast(child); + if (item && !flowItems.Contains(item)) + { + flowItems.Add(item); + needUpdate = true; + } + } + + void GuiFlowComposition::OnChildRemoved(GuiGraphicsComposition* child) + { + GuiBoundsComposition::OnChildRemoved(child); + auto item = dynamic_cast(child); + if (item) + { + flowItems.Remove(item); + needUpdate = true; + } + } + + GuiFlowComposition::GuiFlowComposition() + :axis(new GuiDefaultAxis) + { + BoundsChanged.AttachMethod(this, &GuiFlowComposition::OnBoundsChanged); + } + + GuiFlowComposition::~GuiFlowComposition() + { + } + + const GuiFlowComposition::ItemCompositionList& GuiFlowComposition::GetFlowItems() + { + return flowItems; + } + + bool GuiFlowComposition::InsertFlowItem(vint index, GuiFlowItemComposition* item) + { + index = flowItems.Insert(index, item); + if (!AddChild(item)) + { + flowItems.RemoveAt(index); + return false; + } + else + { + needUpdate = true; + return true; + } + } + + + Margin GuiFlowComposition::GetExtraMargin() + { + return extraMargin; + } + + void GuiFlowComposition::SetExtraMargin(Margin value) + { + extraMargin = value; + needUpdate = true; + InvokeOnCompositionStateChanged(); + } + + vint GuiFlowComposition::GetRowPadding() + { + return rowPadding; + } + + void GuiFlowComposition::SetRowPadding(vint value) + { + rowPadding = value; + needUpdate = true; + InvokeOnCompositionStateChanged(); + } + + vint GuiFlowComposition::GetColumnPadding() + { + return columnPadding; + } + + void GuiFlowComposition::SetColumnPadding(vint value) + { + columnPadding = value; + needUpdate = true; + InvokeOnCompositionStateChanged(); + } + + Ptr GuiFlowComposition::GetAxis() + { + return axis; + } + + void GuiFlowComposition::SetAxis(Ptr value) + { + if (value) + { + axis = value; + needUpdate = true; + InvokeOnCompositionStateChanged(); + } + } + + FlowAlignment GuiFlowComposition::GetAlignment() + { + return alignment; + } + + void GuiFlowComposition::SetAlignment(FlowAlignment value) + { + alignment = value; + needUpdate = true; + InvokeOnCompositionStateChanged(); + } + + void GuiFlowComposition::ForceCalculateSizeImmediately() + { + GuiBoundsComposition::ForceCalculateSizeImmediately(); + UpdateFlowItemBounds(true); + } + + Size GuiFlowComposition::GetMinPreferredClientSize() + { + Size minSize = GuiBoundsComposition::GetMinPreferredClientSize(); + if (GetMinSizeLimitation() == GuiGraphicsComposition::LimitToElementAndChildren) + { + auto clientSize = axis->VirtualSizeToRealSize(Size(0, minHeight)); + FOREACH(GuiFlowItemComposition*, item, flowItems) + { + auto itemSize = item->GetPreferredBounds().GetSize(); + if (clientSize.x < itemSize.x) clientSize.x = itemSize.x; + if (clientSize.y < itemSize.y) clientSize.y = itemSize.y; + } + if (minSize.x < clientSize.x) minSize.x = clientSize.x; + if (minSize.y < clientSize.y) minSize.y = clientSize.y; + } + + vint x = 0; + vint y = 0; + if (extraMargin.left > 0) x += extraMargin.left; + if (extraMargin.right > 0) x += extraMargin.right; + if (extraMargin.top > 0) y += extraMargin.top; + if (extraMargin.bottom > 0) y += extraMargin.bottom; + return minSize + Size(x, y); + } + + Rect GuiFlowComposition::GetBounds() + { + if (!needUpdate) + { + for (vint i = 0; i < flowItems.Count(); i++) + { + if (flowItemBounds[i].GetSize() != flowItems[i]->GetMinSize()) + { + needUpdate = true; + break; + } + } + } + + if (needUpdate) + { + UpdateFlowItemBounds(true); + } + + bounds = GuiBoundsComposition::GetBounds(); + return bounds; + } + +/*********************************************************************** +GuiFlowItemComposition +***********************************************************************/ + + void GuiFlowItemComposition::OnParentChanged(GuiGraphicsComposition* oldParent, GuiGraphicsComposition* newParent) + { + GuiGraphicsSite::OnParentChanged(oldParent, newParent); + flowParent = newParent == 0 ? 0 : dynamic_cast(newParent); + } + + Size GuiFlowItemComposition::GetMinSize() + { + return GetBoundsInternal(bounds).GetSize(); + } + + GuiFlowItemComposition::GuiFlowItemComposition() + { + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + } + + GuiFlowItemComposition::~GuiFlowItemComposition() + { + } + + bool GuiFlowItemComposition::IsSizeAffectParent() + { + return false; + } + + Rect GuiFlowItemComposition::GetBounds() + { + Rect result = bounds; + if(flowParent) + { + flowParent->UpdateFlowItemBounds(false); + vint index = flowParent->flowItems.IndexOf(this); + if (index != -1) + { + result = flowParent->flowItemBounds[index]; + } + + result = Rect( + result.Left() - extraMargin.left, + result.Top() - extraMargin.top, + result.Right() + extraMargin.right, + result.Bottom() + extraMargin.bottom + ); + } + UpdatePreviousBounds(result); + return result; + } + + void GuiFlowItemComposition::SetBounds(Rect value) + { + bounds = value; + InvokeOnCompositionStateChanged(); + } + + Margin GuiFlowItemComposition::GetExtraMargin() + { + return extraMargin; + } + + void GuiFlowItemComposition::SetExtraMargin(Margin value) + { + extraMargin = value; + InvokeOnCompositionStateChanged(); + } + + GuiFlowOption GuiFlowItemComposition::GetFlowOption() + { + return option; + } + + void GuiFlowItemComposition::SetFlowOption(GuiFlowOption value) + { + option = value; + if (flowParent) + { + flowParent->needUpdate = true; + InvokeOnCompositionStateChanged(); + } + } + } + } +} + +/*********************************************************************** +.\GRAPHICSCOMPOSITION\GUIGRAPHICSRESPONSIVECOMPOSITION.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + using namespace collections; + using namespace controls; + +/*********************************************************************** +GuiResponsiveCompositionBase +***********************************************************************/ + + void GuiResponsiveCompositionBase::OnParentLineChanged() + { + GuiBoundsComposition::OnParentLineChanged(); + GuiResponsiveCompositionBase* responsive = nullptr; + { + auto parent = GetParent(); + while (parent) + { + if ((responsive = dynamic_cast(parent))) + { + break; + } + parent = parent->GetParent(); + } + } + + if (responsiveParent != responsive) + { + if (responsiveParent) + { + responsiveParent->OnResponsiveChildRemoved(this); + responsiveParent->OnResponsiveChildLevelUpdated(); + } + responsiveParent = responsive; + if (responsiveParent) + { + responsiveParent->OnResponsiveChildInserted(this); + responsiveParent->OnResponsiveChildLevelUpdated(); + } + } + } + + void GuiResponsiveCompositionBase::OnResponsiveChildInserted(GuiResponsiveCompositionBase* child) + { + } + + void GuiResponsiveCompositionBase::OnResponsiveChildRemoved(GuiResponsiveCompositionBase* child) + { + } + + void GuiResponsiveCompositionBase::OnResponsiveChildLevelUpdated() + { + if (responsiveParent) + { + responsiveParent->OnResponsiveChildLevelUpdated(); + } + else + { + InvokeOnCompositionStateChanged(); + } + } + + GuiResponsiveCompositionBase::GuiResponsiveCompositionBase() + { + SetMinSizeLimitation(LimitToElementAndChildren); + SetPreferredMinSize(Size(1, 1)); + + LevelCountChanged.SetAssociatedComposition(this); + CurrentLevelChanged.SetAssociatedComposition(this); + } + + GuiResponsiveCompositionBase::~GuiResponsiveCompositionBase() + { + } + + ResponsiveDirection GuiResponsiveCompositionBase::GetDirection() + { + return direction; + } + + void GuiResponsiveCompositionBase::SetDirection(ResponsiveDirection value) + { + if (direction != value) + { + direction = value; + OnResponsiveChildLevelUpdated(); + } + } + +/*********************************************************************** +GuiResponsiveSharedCollection +***********************************************************************/ + + void GuiResponsiveSharedCollection::BeforeInsert(vint index, controls::GuiControl* const& value) + { + CHECK_ERROR(!value->GetBoundsComposition()->GetParent(), L"GuiResponsiveSharedCollection::BeforeInsert(vint, GuiResponsiveSharedCollection* const&)#Cannot insert a shared control that is currently in use."); + } + + void GuiResponsiveSharedCollection::AfterInsert(vint index, controls::GuiControl* const& value) + { + view->OnResponsiveChildLevelUpdated(); + } + + void GuiResponsiveSharedCollection::BeforeRemove(vint index, controls::GuiControl* const& value) + { + CHECK_ERROR(!value->GetBoundsComposition()->GetParent(), L"GuiResponsiveSharedCollection::BeforeRemove(vint, GuiResponsiveSharedCollection* const&)#Cannot remove a shared control that is currently in use."); + } + + void GuiResponsiveSharedCollection::AfterRemove(vint index, vint count) + { + view->OnResponsiveChildLevelUpdated(); + } + + GuiResponsiveSharedCollection::GuiResponsiveSharedCollection(GuiResponsiveViewComposition* _view) + :view(_view) + { + } + + GuiResponsiveSharedCollection::~GuiResponsiveSharedCollection() + { + } + +/*********************************************************************** +GuiResponsiveViewCollection +***********************************************************************/ + + void GuiResponsiveViewCollection::BeforeInsert(vint index, GuiResponsiveCompositionBase* const& value) + { + CHECK_ERROR(!value->GetParent(), L"GuiResponsiveViewCollection::BeforeRemove(vint, GuiResponsiveCompositionBase* const&)#Cannot insert a view that is currently in use."); + } + + void GuiResponsiveViewCollection::AfterInsert(vint index, GuiResponsiveCompositionBase* const& value) + { + if (!view->currentView) + { + view->skipUpdatingLevels = true; + view->currentView = value; + view->currentView->SetAlignmentToParent(Margin(0, 0, 0, 0)); + view->AddChild(view->currentView); + view->skipUpdatingLevels = false; + } + view->OnResponsiveChildLevelUpdated(); + } + + void GuiResponsiveViewCollection::BeforeRemove(vint index, GuiResponsiveCompositionBase* const& value) + { + CHECK_ERROR(!value->GetParent(), L"GuiResponsiveViewCollection::BeforeRemove(vint, GuiResponsiveCompositionBase* const&)#Cannot remove a view that is currently in use."); + } + + void GuiResponsiveViewCollection::AfterRemove(vint index, vint count) + { + view->OnResponsiveChildLevelUpdated(); + } + + GuiResponsiveViewCollection::GuiResponsiveViewCollection(GuiResponsiveViewComposition* _view) + :view(_view) + { + } + + GuiResponsiveViewCollection::~GuiResponsiveViewCollection() + { + } + +/*********************************************************************** +GuiResponsiveSharedComposition +***********************************************************************/ + + void GuiResponsiveSharedComposition::SetSharedControl() + { + if (shared && view && !view->destructing) + { + auto sharedParent = shared->GetBoundsComposition()->GetParent(); + CHECK_ERROR(view->sharedControls.Contains(shared), L"GuiResponsiveSharedComposition::SetSharedControl()#The specified shared control is not in GuiResponsiveViewComposition::GetSharedControls()."); + CHECK_ERROR(!sharedParent || sharedParent == this, L"GuiResponsiveSharedComposition::SetSharedControl()#The specified shared control has not been released. This usually means this control is not in GuiResponsiveViewComposition::GetSharedControls()."); + + if (!sharedParent) + { + shared->GetBoundsComposition()->SetAlignmentToParent(Margin(0, 0, 0, 0)); + AddChild(shared->GetBoundsComposition()); + view->usedSharedControls.Add(shared); + } + } + } + + void GuiResponsiveSharedComposition::OnParentLineChanged() + { + GuiBoundsComposition::OnParentLineChanged(); + if (view && view->destructing) + { + return; + } + + GuiResponsiveViewComposition* currentView = nullptr; + { + auto parent = GetParent(); + while (parent) + { + if ((currentView = dynamic_cast(parent))) + { + break; + } + parent = parent->GetParent(); + } + } + + if (currentView != view && view && shared) + { + RemoveChild(shared->GetBoundsComposition()); + view->usedSharedControls.Remove(shared); + } + view = currentView; + + SetSharedControl(); + } + + GuiResponsiveSharedComposition::GuiResponsiveSharedComposition() + { + SetMinSizeLimitation(LimitToElementAndChildren); + } + + GuiResponsiveSharedComposition::~GuiResponsiveSharedComposition() + { + } + + controls::GuiControl* GuiResponsiveSharedComposition::GetShared() + { + return shared; + } + + void GuiResponsiveSharedComposition::SetShared(controls::GuiControl* value) + { + if (shared != value) + { + CHECK_ERROR(!shared || !shared->GetBoundsComposition()->GetParent(), L"GuiResponsiveSharedComposition::SetShared(GuiControl*)#Cannot replace a shared control that is currently in use."); + shared = value; + SetSharedControl(); + } + } + +/*********************************************************************** +GuiResponsiveViewComposition +***********************************************************************/ + + bool GuiResponsiveViewComposition::CalculateLevelCount() + { + vint old = levelCount; + if (views.Count() == 0) + { + levelCount = 1; + } + else + { + levelCount = 0; + for (vint i = 0; i < views.Count(); i++) + { + auto view = views[i]; + if (((vint)direction & (vint)view->GetDirection()) != 0) + { + levelCount += view->GetLevelCount(); + } + else + { + levelCount += 1; + } + } + } + + if (old != levelCount) + { + LevelCountChanged.Execute(GuiEventArgs(this)); + return true; + } + return false; + } + + bool GuiResponsiveViewComposition::CalculateCurrentLevel() + { + vint old = currentLevel; + currentLevel = 0; + for (vint i = views.Count() - 1; i >= 0; i--) + { + auto view = views[i]; + if (((vint)direction & (vint)view->GetDirection()) != 0) + { + if (currentView == view) + { + currentLevel += view->GetCurrentLevel() + 1; + break; + } + else + { + currentLevel += view->GetLevelCount(); + } + } + else + { + currentLevel++; + } + } + currentLevel--; + + if (old != currentLevel) + { + CurrentLevelChanged.Execute(GuiEventArgs(this)); + return true; + } + return false; + } + + void GuiResponsiveViewComposition::OnResponsiveChildLevelUpdated() + { + if (!skipUpdatingLevels) + { + CalculateLevelCount(); + CalculateCurrentLevel(); + GuiResponsiveCompositionBase::OnResponsiveChildLevelUpdated(); + } + } + + GuiResponsiveViewComposition::GuiResponsiveViewComposition() + :sharedControls(this) + , views(this) + { + BeforeSwitchingView.SetAssociatedComposition(this); + } + + GuiResponsiveViewComposition::~GuiResponsiveViewComposition() + { + destructing = true; + + FOREACH(GuiResponsiveCompositionBase*, view, views) + { + if (view != currentView) + { + SafeDeleteComposition(view); + } + } + + FOREACH(GuiControl*, shared, From(sharedControls).Except(usedSharedControls)) + { + SafeDeleteControl(shared); + } + } + + vint GuiResponsiveViewComposition::GetLevelCount() + { + return levelCount; + } + + vint GuiResponsiveViewComposition::GetCurrentLevel() + { + return currentLevel; + } + + bool GuiResponsiveViewComposition::LevelDown() + { + skipUpdatingLevels = true; + if (((vint)direction & (vint)currentView->GetDirection()) != 0 && !currentView->LevelDown()) + { + vint index = views.IndexOf(currentView); + if (index < views.Count() - 1) + { + RemoveChild(currentView); + currentView = views[index + 1]; + currentView->SetAlignmentToParent(Margin(0, 0, 0, 0)); + { + GuiItemEventArgs arguments(this); + arguments.itemIndex = views.IndexOf(currentView); + BeforeSwitchingView.Execute(arguments); + } + AddChild(currentView); + } + } + skipUpdatingLevels = false; + + auto x = CalculateLevelCount(); + auto y = CalculateCurrentLevel(); + if (!x && !y) return false; + InvokeOnCompositionStateChanged(); + return true; + } + + bool GuiResponsiveViewComposition::LevelUp() + { + skipUpdatingLevels = true; + if (((vint)direction & (vint)currentView->GetDirection()) != 0 && !currentView->LevelUp()) + { + vint index = views.IndexOf(currentView); + if (index > 0) + { + RemoveChild(currentView); + currentView = views[index - 1]; + currentView->SetAlignmentToParent(Margin(0, 0, 0, 0)); + { + GuiItemEventArgs arguments(this); + arguments.itemIndex = views.IndexOf(currentView); + BeforeSwitchingView.Execute(arguments); + } + AddChild(currentView); + } + } + skipUpdatingLevels = false; + + auto x = CalculateLevelCount(); + auto y = CalculateCurrentLevel(); + if (!x && !y) return false; + InvokeOnCompositionStateChanged(); + return true; + } + + GuiResponsiveCompositionBase* GuiResponsiveViewComposition::GetCurrentView() + { + return currentView; + } + + collections::ObservableListBase& GuiResponsiveViewComposition::GetSharedControls() + { + return sharedControls; + } + + collections::ObservableListBase& GuiResponsiveViewComposition::GetViews() + { + return views; + } + +/*********************************************************************** +GuiResponsiveFixedComposition +***********************************************************************/ + + void GuiResponsiveFixedComposition::OnResponsiveChildLevelUpdated() + { + InvokeOnCompositionStateChanged(); + } + + GuiResponsiveFixedComposition::GuiResponsiveFixedComposition() + { + } + + GuiResponsiveFixedComposition::~GuiResponsiveFixedComposition() + { + } + + vint GuiResponsiveFixedComposition::GetLevelCount() + { + return 1; + } + + vint GuiResponsiveFixedComposition::GetCurrentLevel() + { + return 0; + } + + bool GuiResponsiveFixedComposition::LevelDown() + { + return false; + } + + bool GuiResponsiveFixedComposition::LevelUp() + { + return false; + } + +/*********************************************************************** +GuiResponsiveStackComposition +***********************************************************************/ + +#define DEFINE_AVAILABLE \ + auto availables = From(responsiveChildren) \ + .Where([=](GuiResponsiveCompositionBase* child) \ + { \ + return ((vint)direction & (vint)child->GetDirection()) != 0; \ + }) \ + + bool GuiResponsiveStackComposition::CalculateLevelCount() + { + vint old = levelCount; + DEFINE_AVAILABLE; + if (availables.IsEmpty()) + { + levelCount = 1; + } + else + { + levelCount = availables + .Select([](GuiResponsiveCompositionBase* child) + { + return child->GetLevelCount() - 1; + }) + .Aggregate([](vint a, vint b) + { + return a + b; + }) + 1; + } + + if (old != levelCount) + { + LevelCountChanged.Execute(GuiEventArgs(this)); + return true; + } + return false; + } + + bool GuiResponsiveStackComposition::CalculateCurrentLevel() + { + vint old = currentLevel; + DEFINE_AVAILABLE; + if (availables.IsEmpty()) + { + currentLevel = 0; + } + else + { + currentLevel = availables + .Select([](GuiResponsiveCompositionBase* child) + { + return child->GetCurrentLevel(); + }) + .Aggregate([](vint a, vint b) + { + return a + b; + }); + } + + if (old != currentLevel) + { + CurrentLevelChanged.Execute(GuiEventArgs(this)); + return true; + } + return false; + } + + void GuiResponsiveStackComposition::OnResponsiveChildInserted(GuiResponsiveCompositionBase* child) + { + responsiveChildren.Add(child); + } + + void GuiResponsiveStackComposition::OnResponsiveChildRemoved(GuiResponsiveCompositionBase* child) + { + responsiveChildren.Remove(child); + } + + void GuiResponsiveStackComposition::OnResponsiveChildLevelUpdated() + { + CalculateLevelCount(); + CalculateCurrentLevel(); + GuiResponsiveCompositionBase::OnResponsiveChildLevelUpdated(); + } + + bool GuiResponsiveStackComposition::ChangeLevel(bool levelDown) + { + DEFINE_AVAILABLE; + + SortedList ignored; + while (true) + { + GuiResponsiveCompositionBase* selected = nullptr; + vint size = 0; + + FOREACH(GuiResponsiveCompositionBase*, child, availables) + { + if (!ignored.Contains(child)) + { + Size childSize = child->GetPreferredBounds().GetSize(); + vint childSizeToCompare = + direction == ResponsiveDirection::Horizontal ? childSize.x : + direction == ResponsiveDirection::Vertical ? childSize.y : + childSize.x * childSize.y; + + if (!selected || (levelDown ? size < childSizeToCompare : size > childSizeToCompare)) + { + selected = child; + size = childSizeToCompare; + } + } + } + + if (!selected) + { + break; + } + else if (levelDown ? selected->LevelDown() : selected->LevelUp()) + { + break; + } + else + { + ignored.Add(selected); + } + } + + if (!CalculateCurrentLevel()) return false; + InvokeOnCompositionStateChanged(); + return true; + } + + GuiResponsiveStackComposition::GuiResponsiveStackComposition() + { + } + + GuiResponsiveStackComposition::~GuiResponsiveStackComposition() + { + } + + vint GuiResponsiveStackComposition::GetLevelCount() + { + return levelCount; + } + + vint GuiResponsiveStackComposition::GetCurrentLevel() + { + return currentLevel; + } + + bool GuiResponsiveStackComposition::LevelDown() + { + return ChangeLevel(true); + } + + bool GuiResponsiveStackComposition::LevelUp() + { + return ChangeLevel(false); + } + +/*********************************************************************** +GuiResponsiveGroupComposition +***********************************************************************/ + + bool GuiResponsiveGroupComposition::CalculateLevelCount() + { + vint old = levelCount; + DEFINE_AVAILABLE; + if (availables.IsEmpty()) + { + levelCount = 1; + } + else + { + levelCount = availables + .Select([](GuiResponsiveCompositionBase* child) + { + return child->GetLevelCount(); + }) + .Max(); + } + + if (old != levelCount) + { + LevelCountChanged.Execute(GuiEventArgs(this)); + return true; + } + return false; + } + + bool GuiResponsiveGroupComposition::CalculateCurrentLevel() + { + vint old = currentLevel; + DEFINE_AVAILABLE; + if (availables.IsEmpty()) + { + currentLevel = 0; + } + else + { + currentLevel = availables + .Select([](GuiResponsiveCompositionBase* child) + { + return child->GetCurrentLevel(); + }) + .Max(); + } + + if (old != currentLevel) + { + CurrentLevelChanged.Execute(GuiEventArgs(this)); + return true; + } + return false; + } + + void GuiResponsiveGroupComposition::OnResponsiveChildInserted(GuiResponsiveCompositionBase* child) + { + responsiveChildren.Add(child); + } + + void GuiResponsiveGroupComposition::OnResponsiveChildRemoved(GuiResponsiveCompositionBase* child) + { + responsiveChildren.Remove(child); + } + + void GuiResponsiveGroupComposition::OnResponsiveChildLevelUpdated() + { + CalculateLevelCount(); + CalculateCurrentLevel(); + GuiResponsiveCompositionBase::OnResponsiveChildLevelUpdated(); + } + + GuiResponsiveGroupComposition::GuiResponsiveGroupComposition() + { + } + + GuiResponsiveGroupComposition::~GuiResponsiveGroupComposition() + { + } + + vint GuiResponsiveGroupComposition::GetLevelCount() + { + return levelCount; + } + + vint GuiResponsiveGroupComposition::GetCurrentLevel() + { + return currentLevel; + } + + bool GuiResponsiveGroupComposition::LevelDown() + { + DEFINE_AVAILABLE; + vint level = currentLevel; + FOREACH(GuiResponsiveCompositionBase*, child, availables) + { + if (child->GetCurrentLevel() >= level) + { + if (!child->LevelDown()) + { + break; + } + } + } + + if (!CalculateCurrentLevel()) return false; + InvokeOnCompositionStateChanged(); + return true; + } + + bool GuiResponsiveGroupComposition::LevelUp() + { + DEFINE_AVAILABLE; + vint level = currentLevel; + FOREACH(GuiResponsiveCompositionBase*, child, availables) + { + while (child->GetCurrentLevel() <= level) + { + if (!child->LevelUp()) + { + break; + } + } + } + + if (!CalculateCurrentLevel()) return false; + InvokeOnCompositionStateChanged(); + return true; + } + +#undef DEFINE_AVAILABLE + +/*********************************************************************** +GuiResponsiveContainerComposition +***********************************************************************/ + +#define RESPONSIVE_INVALID_SIZE Size(-1, -1) + + void GuiResponsiveContainerComposition::AdjustLevel() + { + if (!responsiveTarget) return; + const Size containerSize = GetBounds().GetSize(); + const Size responsiveOriginalSize = responsiveTarget->GetPreferredBounds().GetSize(); + const bool testX = (vint)responsiveTarget->GetDirection() & (vint)ResponsiveDirection::Horizontal; + const bool testY = (vint)responsiveTarget->GetDirection() & (vint)ResponsiveDirection::Vertical; + +#define RESPONSIVE_IF_CONTAINER(OP, SIZE) ((testX && (containerSize).x OP SIZE.x) || (testY && (containerSize).y OP SIZE.y)) + + if (upperLevelSize != RESPONSIVE_INVALID_SIZE && RESPONSIVE_IF_CONTAINER(>=, upperLevelSize)) + { + upperLevelSize = RESPONSIVE_INVALID_SIZE; + } + + if (upperLevelSize == RESPONSIVE_INVALID_SIZE && RESPONSIVE_IF_CONTAINER(>=, responsiveOriginalSize)) + { + while (true) + { + if (responsiveTarget->GetCurrentLevel() == responsiveTarget->GetLevelCount() - 1) + { + break; + } + else if (responsiveTarget->LevelUp()) + { + responsiveTarget->ForceCalculateSizeImmediately(); + auto currentSize = responsiveTarget->GetPreferredBounds().GetSize(); + if (RESPONSIVE_IF_CONTAINER(<, currentSize)) + { + upperLevelSize = currentSize; + responsiveTarget->LevelDown(); + break; + } + } + else + { + break; + } + } + } + else + { + while (true) + { + responsiveTarget->ForceCalculateSizeImmediately(); + auto currentSize = responsiveTarget->GetPreferredBounds().GetSize(); + if (RESPONSIVE_IF_CONTAINER(>=, currentSize)) + { + break; + } + + if (responsiveTarget->GetCurrentLevel() == 0) + { + break; + } + else if(responsiveTarget->LevelDown()) + { + upperLevelSize = currentSize; + } + else + { + break; + } + } + } + +#undef RESPONSIVE_IF_CONTAINER + } + + void GuiResponsiveContainerComposition::OnBoundsChanged(GuiGraphicsComposition* sender, GuiEventArgs& arguments) + { + auto control = GetRelatedControl(); + if (control) + { + control->InvokeOrDelayIfRendering([=]() + { + AdjustLevel(); + }); + } + else + { + AdjustLevel(); + } + } + + GuiResponsiveContainerComposition::GuiResponsiveContainerComposition() + :upperLevelSize(RESPONSIVE_INVALID_SIZE) + { + BoundsChanged.AttachMethod(this, &GuiResponsiveContainerComposition::OnBoundsChanged); + } + + GuiResponsiveContainerComposition::~GuiResponsiveContainerComposition() + { + } + + GuiResponsiveCompositionBase* GuiResponsiveContainerComposition::GetResponsiveTarget() + { + return responsiveTarget; + } + + void GuiResponsiveContainerComposition::SetResponsiveTarget(GuiResponsiveCompositionBase* value) + { + if (responsiveTarget != value) + { + if (responsiveTarget) + { + RemoveChild(responsiveTarget); + } + + responsiveTarget = value; + upperLevelSize = RESPONSIVE_INVALID_SIZE; + + if (responsiveTarget) + { + responsiveTarget->SetAlignmentToParent(Margin(0, 0, 0, 0)); + while (responsiveTarget->LevelUp()); + AddChild(responsiveTarget); + + GuiEventArgs arguments(this); + OnBoundsChanged(this, arguments); + } + } + } + +#undef RESPONSIVE_INVALID_SIZE + } + } +} + + +/*********************************************************************** +.\GRAPHICSCOMPOSITION\GUIGRAPHICSSPECIALIZEDCOMPOSITION.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + +/*********************************************************************** +GuiSideAlignedComposition +***********************************************************************/ + + GuiSideAlignedComposition::GuiSideAlignedComposition() + :direction(Top) + ,maxLength(10) + ,maxRatio(1.0) + { + } + + GuiSideAlignedComposition::~GuiSideAlignedComposition() + { + } + + GuiSideAlignedComposition::Direction GuiSideAlignedComposition::GetDirection() + { + return direction; + } + + void GuiSideAlignedComposition::SetDirection(Direction value) + { + direction = value; + InvokeOnCompositionStateChanged(); + } + + vint GuiSideAlignedComposition::GetMaxLength() + { + return maxLength; + } + + void GuiSideAlignedComposition::SetMaxLength(vint value) + { + if (value < 0) value = 0; + maxLength = value; + InvokeOnCompositionStateChanged(); + } + + double GuiSideAlignedComposition::GetMaxRatio() + { + return maxRatio; + } + + void GuiSideAlignedComposition::SetMaxRatio(double value) + { + maxRatio = + value < 0 ? 0 : + value>1 ? 1 : + value; + InvokeOnCompositionStateChanged(); + } + + bool GuiSideAlignedComposition::IsSizeAffectParent() + { + return false; + } + + Rect GuiSideAlignedComposition::GetBounds() + { + Rect result; + GuiGraphicsComposition* parent = GetParent(); + if (parent) + { + Rect bounds = parent->GetBounds(); + vint w = (vint)(bounds.Width()*maxRatio); + vint h = (vint)(bounds.Height()*maxRatio); + if (w > maxLength) w = maxLength; + if (h > maxLength) h = maxLength; + switch (direction) + { + case Left: + { + bounds.x2 = bounds.x1 + w; + } + break; + case Top: + { + bounds.y2 = bounds.y1 + h; + } + break; + case Right: + { + bounds.x1 = bounds.x2 - w; + } + break; + case Bottom: + { + bounds.y1 = bounds.y2 - h; + } + break; + } + result = bounds; + } + UpdatePreviousBounds(result); + return result; + } + +/*********************************************************************** +GuiPartialViewComposition +***********************************************************************/ + + GuiPartialViewComposition::GuiPartialViewComposition() + :wRatio(0.0) + ,wPageSize(1.0) + ,hRatio(0.0) + ,hPageSize(1.0) + { + } + + GuiPartialViewComposition::~GuiPartialViewComposition() + { + } + + double GuiPartialViewComposition::GetWidthRatio() + { + return wRatio; + } + + double GuiPartialViewComposition::GetWidthPageSize() + { + return wPageSize; + } + + double GuiPartialViewComposition::GetHeightRatio() + { + return hRatio; + } + + double GuiPartialViewComposition::GetHeightPageSize() + { + return hPageSize; + } + + void GuiPartialViewComposition::SetWidthRatio(double value) + { + wRatio = value; + InvokeOnCompositionStateChanged(); + } + + void GuiPartialViewComposition::SetWidthPageSize(double value) + { + wPageSize = value; + InvokeOnCompositionStateChanged(); + } + + void GuiPartialViewComposition::SetHeightRatio(double value) + { + hRatio = value; + InvokeOnCompositionStateChanged(); + } + + void GuiPartialViewComposition::SetHeightPageSize(double value) + { + hPageSize = value; + InvokeOnCompositionStateChanged(); + } + + bool GuiPartialViewComposition::IsSizeAffectParent() + { + return false; + } + + Rect GuiPartialViewComposition::GetBounds() + { + Rect result; + GuiGraphicsComposition* parent = GetParent(); + if (parent) + { + Rect bounds = parent->GetBounds(); + vint w = bounds.Width(); + vint h = bounds.Height(); + vint pw = (vint)(wPageSize*w); + vint ph = (vint)(hPageSize*h); + + vint ow = preferredMinSize.x - pw; + if (ow < 0) ow = 0; + vint oh = preferredMinSize.y - ph; + if (oh < 0) oh = 0; + + w -= ow; + h -= oh; + pw += ow; + ph += oh; + + result = Rect(Point((vint)(wRatio*w), (vint)(hRatio*h)), Size(pw, ph)); + } + UpdatePreviousBounds(result); + return result; + } + } + } +} + + +/*********************************************************************** +.\GRAPHICSCOMPOSITION\GUIGRAPHICSSTACKCOMPOSITION.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + +/*********************************************************************** +GuiStackComposition +***********************************************************************/ + + void GuiStackComposition::UpdateStackItemBounds() + { + if (stackItemBounds.Count() != stackItems.Count()) + { + stackItemBounds.Resize(stackItems.Count()); + } + + stackItemTotalSize = Size(0, 0); + Point offset; + for (vint i = 0; i < stackItems.Count(); i++) + { + vint offsetX = 0; + vint offsetY = 0; + Size itemSize = stackItems[i]->GetMinSize(); + stackItemBounds[i] = Rect(offset, itemSize); + +#define ACCUMULATE(U, V) \ + { \ + if (stackItemTotalSize.V < itemSize.V) \ + { \ + stackItemTotalSize.V = itemSize.V; \ + } \ + if (i > 0) \ + { \ + stackItemTotalSize.U += padding; \ + } \ + stackItemTotalSize.U += itemSize.U; \ + } \ + + switch (direction) + { + case GuiStackComposition::Horizontal: + case GuiStackComposition::ReversedHorizontal: + ACCUMULATE(x, y) + break; + case GuiStackComposition::Vertical: + case GuiStackComposition::ReversedVertical: + ACCUMULATE(y, x) + break; + } + +#undef ACCUMULATE + offset.x += itemSize.x + padding; + offset.y += itemSize.y + padding; + } + EnsureStackItemVisible(); + } + + void GuiStackComposition::EnsureStackItemVisible() + { +#define ADJUSTMENT(U, V) \ + if (itemBounds.U() <= 0) \ + { \ + adjustment -= itemBounds.U(); \ + } \ + else \ + { \ + vint overflow = itemBounds.V() - previousBounds.V(); \ + if (overflow > 0) \ + { \ + adjustment -= overflow; \ + } \ + } \ + + if (ensuringVisibleStackItem) + { + Rect itemBounds = ensuringVisibleStackItem->GetBounds(); + switch (direction) + { + case Horizontal: + case ReversedHorizontal: + ADJUSTMENT(Left, Right) + break; + case Vertical: + case ReversedVertical: + ADJUSTMENT(Top, Bottom) + break; + } + } + + InvokeOnCompositionStateChanged(); +#undef ADJUSTMENT + } + + void GuiStackComposition::OnBoundsChanged(GuiGraphicsComposition* sender, GuiEventArgs& arguments) + { + EnsureStackItemVisible(); + } + + void GuiStackComposition::OnChildInserted(GuiGraphicsComposition* child) + { + GuiBoundsComposition::OnChildInserted(child); + GuiStackItemComposition* item = dynamic_cast(child); + if (item) + { + if (!stackItems.Contains(item)) + { + stackItems.Add(item); + } + UpdateStackItemBounds(); + } + } + + void GuiStackComposition::OnChildRemoved(GuiGraphicsComposition* child) + { + GuiBoundsComposition::OnChildRemoved(child); + GuiStackItemComposition* item = dynamic_cast(child); + if (item) + { + stackItems.Remove(item); + if (item == ensuringVisibleStackItem) + { + ensuringVisibleStackItem = 0; + } + UpdateStackItemBounds(); + } + } + + GuiStackComposition::GuiStackComposition() + { + BoundsChanged.AttachMethod(this, &GuiStackComposition::OnBoundsChanged); + } + + GuiStackComposition::~GuiStackComposition() + { + } + + const GuiStackComposition::ItemCompositionList& GuiStackComposition::GetStackItems() + { + return stackItems; + } + + bool GuiStackComposition::InsertStackItem(vint index, GuiStackItemComposition* item) + { + index = stackItems.Insert(index, item); + if (!AddChild(item)) + { + stackItems.RemoveAt(index); + return false; + } + else + { + return true; + } + } + + GuiStackComposition::Direction GuiStackComposition::GetDirection() + { + return direction; + } + + void GuiStackComposition::SetDirection(Direction value) + { + direction = value; + EnsureStackItemVisible(); + } + + vint GuiStackComposition::GetPadding() + { + return padding; + } + + void GuiStackComposition::SetPadding(vint value) + { + padding = value; + EnsureStackItemVisible(); + } + + void GuiStackComposition::ForceCalculateSizeImmediately() + { + GuiBoundsComposition::ForceCalculateSizeImmediately(); + UpdateStackItemBounds(); + } + + Size GuiStackComposition::GetMinPreferredClientSize() + { + Size minSize = GuiBoundsComposition::GetMinPreferredClientSize(); + if (GetMinSizeLimitation() == GuiGraphicsComposition::LimitToElementAndChildren) + { + if (!ensuringVisibleStackItem || direction == Vertical || direction == ReversedVertical) + { + if (minSize.x < stackItemTotalSize.x) + { + minSize.x = stackItemTotalSize.x; + } + } + if (!ensuringVisibleStackItem || direction == Horizontal || direction == ReversedHorizontal) + { + if (minSize.y < stackItemTotalSize.y) + { + minSize.y = stackItemTotalSize.y; + } + } + } + + vint x = 0; + vint y = 0; + if (extraMargin.left > 0) x += extraMargin.left; + if (extraMargin.right > 0) x += extraMargin.right; + if (extraMargin.top > 0) y += extraMargin.top; + if (extraMargin.bottom > 0) y += extraMargin.bottom; + return minSize + Size(x, y); + } + + Rect GuiStackComposition::GetBounds() + { + for (vint i = 0; i < stackItems.Count(); i++) + { + if (stackItemBounds[i].GetSize() != stackItems[i]->GetMinSize()) + { + UpdateStackItemBounds(); + break; + } + } + + Rect bounds = GuiBoundsComposition::GetBounds(); + previousBounds = bounds; + UpdatePreviousBounds(previousBounds); + return bounds; + } + + Margin GuiStackComposition::GetExtraMargin() + { + return extraMargin; + } + + void GuiStackComposition::SetExtraMargin(Margin value) + { + extraMargin=value; + EnsureStackItemVisible(); + } + + bool GuiStackComposition::IsStackItemClipped() + { + Rect clientArea = GetClientArea(); + switch(direction) + { + case Horizontal: + case ReversedHorizontal: + { + vint width = stackItemTotalSize.x + + (extraMargin.left > 0 ? extraMargin.left : 0) + + (extraMargin.right > 0 ? extraMargin.right : 0) + ; + return width > clientArea.Width(); + } + break; + case Vertical: + case ReversedVertical: + { + vint height = stackItemTotalSize.y + + (extraMargin.top > 0 ? extraMargin.top : 0) + + (extraMargin.bottom > 0 ? extraMargin.bottom : 0) + ; + return height > clientArea.Height(); + } + break; + } + return false; + } + + bool GuiStackComposition::EnsureVisible(vint index) + { + if (0 <= index && index < stackItems.Count()) + { + ensuringVisibleStackItem = stackItems[index]; + } + else + { + ensuringVisibleStackItem = 0; + } + EnsureStackItemVisible(); + return ensuringVisibleStackItem != 0; + } + +/*********************************************************************** +GuiStackItemComposition +***********************************************************************/ + + void GuiStackItemComposition::OnParentChanged(GuiGraphicsComposition* oldParent, GuiGraphicsComposition* newParent) + { + GuiGraphicsSite::OnParentChanged(oldParent, newParent); + stackParent = newParent == 0 ? 0 : dynamic_cast(newParent); + } + + Size GuiStackItemComposition::GetMinSize() + { + return GetBoundsInternal(bounds).GetSize(); + } + + GuiStackItemComposition::GuiStackItemComposition() + :stackParent(0) + { + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + } + + GuiStackItemComposition::~GuiStackItemComposition() + { + } + + bool GuiStackItemComposition::IsSizeAffectParent() + { + return false; + } + + Rect GuiStackItemComposition::GetBounds() + { + Rect result = bounds; + if(stackParent) + { + vint index = stackParent->stackItems.IndexOf(this); + if (index != -1) + { + result = stackParent->stackItemBounds[index]; + } + + Rect parentBounds = stackParent->previousBounds; + Margin margin = stackParent->extraMargin; + if (margin.left <= 0) margin.left = 0; + if (margin.top <= 0) margin.top = 0; + if (margin.right <= 0) margin.right = 0; + if (margin.bottom <= 0) margin.bottom = 0; + + auto x = result.Left(); + auto y = result.Top(); + auto w = result.Width(); + auto h = result.Height(); + + switch (stackParent->direction) + { + case GuiStackComposition::Horizontal: + x += margin.left + stackParent->adjustment; + y = margin.top; + h = parentBounds.Height() - margin.top - margin.bottom; + break; + case GuiStackComposition::ReversedHorizontal: + x = parentBounds.Width() - margin.right - x - w + stackParent->adjustment; + y = margin.top; + h = parentBounds.Height() - margin.top - margin.bottom; + break; + case GuiStackComposition::Vertical: + x = margin.left; + y += margin.top + stackParent->adjustment; + w = parentBounds.Width() - margin.left - margin.right; + break; + case GuiStackComposition::ReversedVertical: + x = margin.left; + y = parentBounds.Height() - margin.bottom - y - h + stackParent->adjustment; + w = parentBounds.Width() - margin.left - margin.right; + break; + } + + result = Rect( + x - extraMargin.left, + y - extraMargin.top, + x + w + extraMargin.right, + y + h + extraMargin.bottom + ); + } + UpdatePreviousBounds(result); + return result; + } + + void GuiStackItemComposition::SetBounds(Rect value) + { + bounds = value; + InvokeOnCompositionStateChanged(); + } + + Margin GuiStackItemComposition::GetExtraMargin() + { + return extraMargin; + } + + void GuiStackItemComposition::SetExtraMargin(Margin value) + { + extraMargin = value; + InvokeOnCompositionStateChanged(); + } + } + } +} + + +/*********************************************************************** +.\GRAPHICSCOMPOSITION\GUIGRAPHICSTABLECOMPOSITION.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + using namespace collections; + using namespace controls; + using namespace elements; + +/*********************************************************************** +GuiTableComposition +***********************************************************************/ + + namespace update_cell_bounds_helpers + { + vint First(vint a, vint b) + { + return a; + } + + vint Second(vint a, vint b) + { + return b; + } + + vint X(Size s) + { + return s.x; + } + + vint Y(Size s) + { + return s.y; + } + + vint RL(GuiCellComposition* cell) + { + return cell->GetRow(); + } + + vint CL(GuiCellComposition* cell) + { + return cell->GetColumn(); + } + + vint RS(GuiCellComposition* cell) + { + return cell->GetRowSpan(); + } + + vint CS(GuiCellComposition* cell) + { + return cell->GetColumnSpan(); + } + } + using namespace update_cell_bounds_helpers; + + vint GuiTableComposition::GetSiteIndex(vint _rows, vint _columns, vint _row, vint _column) + { + return _row*_columns + _column; + } + + void GuiTableComposition::SetSitedCell(vint _row, vint _column, GuiCellComposition* cell) + { + cellCompositions[GetSiteIndex(rows, columns, _row, _column)] = cell; + } + + void GuiTableComposition::UpdateCellBoundsInternal( + collections::Array& dimSizes, + vint& dimSize, + vint& dimSizeWithPercentage, + collections::Array& dimOptions, + vint GuiTableComposition::* dim1, + vint GuiTableComposition::* dim2, + vint(*getSize)(Size), + vint(*getLocation)(GuiCellComposition*), + vint(*getSpan)(GuiCellComposition*), + vint(*getRow)(vint, vint), + vint(*getCol)(vint, vint), + vint maxPass + ) + { + for (vint pass = 0; pass < maxPass; pass++) + { + for (vint i = 0; i < this->*dim1; i++) + { + GuiCellOption option = dimOptions[i]; + if (pass == 0) + { + dimSizes[i] = 0; + } + switch (option.composeType) + { + case GuiCellOption::Absolute: + { + dimSizes[i] = option.absolute; + } + break; + case GuiCellOption::MinSize: + { + for (vint j = 0; j < this->*dim2; j++) + { + GuiCellComposition* cell = GetSitedCell(getRow(i, j), getCol(i, j)); + if (cell) + { + bool accept = false; + if (pass == 0) + { + accept = getSpan(cell) == 1; + } + else + { + accept = getLocation(cell) + getSpan(cell) == i + 1; + } + if (accept) + { + vint size = getSize(cell->GetPreferredBounds().GetSize()); + vint span = getSpan(cell); + for (vint k = 1; k < span; k++) + { + size -= dimSizes[i - k] + cellPadding; + } + if (dimSizes[i] < size) + { + dimSizes[i] = size; + } + } + } + } + } + break; + default:; + } + } + } + + bool percentageExists = false; + for (vint i = 0; i < this->*dim1; i++) + { + GuiCellOption option = dimOptions[i]; + if (option.composeType == GuiCellOption::Percentage) + { + if (0.001 < option.percentage) + { + percentageExists = true; + } + } + } + + if (percentageExists) + { + for (vint i = 0; i < this->*dim1; i++) + { + GuiCellOption option = dimOptions[i]; + if (option.composeType == GuiCellOption::Percentage) + { + if (0.001 < option.percentage) + { + for (vint j = 0; j < this->*dim2; j++) + { + GuiCellComposition* cell = GetSitedCell(getRow(i, j), getCol(i, j)); + if (cell) + { + vint size = getSize(cell->GetPreferredBounds().GetSize()); + vint start = getLocation(cell); + vint span = getSpan(cell); + size -= (span - 1)*cellPadding; + double totalPercentage = 0; + + for (vint k = start; k < start + span; k++) + { + if (dimOptions[k].composeType == GuiCellOption::Percentage) + { + if (0.001 < dimOptions[k].percentage) + { + totalPercentage += dimOptions[k].percentage; + } + } + else + { + size -= dimSizes[k]; + } + } + + size = (vint)ceil(size*option.percentage / totalPercentage); + if (dimSizes[i] < size) + { + dimSizes[i] = size; + } + } + } + } + } + } + + vint percentageTotalSize = 0; + for (vint i = 0; i < this->*dim1; i++) + { + GuiCellOption option = dimOptions[i]; + if (option.composeType == GuiCellOption::Percentage) + { + if (0.001 < option.percentage) + { + vint size = (vint)ceil(dimSizes[i] / option.percentage); + if (percentageTotalSize < size) + { + percentageTotalSize = size; + } + } + } + } + + double totalPercentage = 0; + for (vint i = 0; i < this->*dim1; i++) + { + GuiCellOption option = dimOptions[i]; + if (option.composeType == GuiCellOption::Percentage) + { + if (0.001 < option.percentage) + { + totalPercentage += option.percentage; + } + } + } + + for (vint i = 0; i < this->*dim1; i++) + { + GuiCellOption option = dimOptions[i]; + if (option.composeType == GuiCellOption::Percentage) + { + if (0.001 < option.percentage) + { + vint size = (vint)ceil(percentageTotalSize*option.percentage / totalPercentage); + if (dimSizes[i] < size) + { + dimSizes[i] = size; + } + } + } + } + } + + for (vint i = 0; i < this->*dim1; i++) + { + if (dimOptions[i].composeType != GuiCellOption::Percentage) + { + dimSize += dimSizes[i]; + } + dimSizeWithPercentage += dimSizes[i]; + } + } + + void GuiTableComposition::UpdateCellBoundsPercentages( + collections::Array& dimSizes, + vint dimSize, + vint maxDimSize, + collections::Array& dimOptions + ) + { + if (maxDimSize > dimSize) + { + double totalPercentage = 0; + vint percentageCount = 0; + for (vint i = 0; i < dimOptions.Count(); i++) + { + GuiCellOption option = dimOptions[i]; + if (option.composeType == GuiCellOption::Percentage) + { + totalPercentage += option.percentage; + percentageCount++; + } + } + if (percentageCount > 0 && totalPercentage > 0.001) + { + for (vint i = 0; i < dimOptions.Count(); i++) + { + GuiCellOption option = dimOptions[i]; + if (option.composeType == GuiCellOption::Percentage) + { + dimSizes[i] = (vint)((maxDimSize - dimSize)*option.percentage / totalPercentage); + } + } + } + } + } + + vint GuiTableComposition::UpdateCellBoundsOffsets( + collections::Array& offsets, + collections::Array& sizes, + vint max + ) + { + offsets[0] = 0; + for (vint i = 1; i < offsets.Count(); i++) + { + offsets[i] = offsets[i - 1] + cellPadding + sizes[i - 1]; + } + + vint last = offsets.Count() - 1; + vint right = offsets[last] + sizes[last]; + return max - right; + } + + void GuiTableComposition::OnRenderContextChanged() + { + if(GetRenderTarget()) + { + UpdateCellBounds(); + } + } + + GuiTableComposition::GuiTableComposition() + :rows(0) + , columns(0) + , cellPadding(0) + , borderVisible(true) + , rowExtending(0) + , columnExtending(0) + { + ConfigChanged.SetAssociatedComposition(this); + SetRowsAndColumns(1, 1); + } + + GuiTableComposition::~GuiTableComposition() + { + } + + vint GuiTableComposition::GetRows() + { + return rows; + } + + vint GuiTableComposition::GetColumns() + { + return columns; + } + + bool GuiTableComposition::SetRowsAndColumns(vint _rows, vint _columns) + { + if (_rows <= 0 || _columns <= 0) return false; + rowOptions.Resize(_rows); + columnOptions.Resize(_columns); + cellCompositions.Resize(_rows*_columns); + cellBounds.Resize(_rows*_columns); + for (vint i = 0; i < _rows*_columns; i++) + { + cellCompositions[i] = 0; + cellBounds[i] = Rect(); + } + rows = _rows; + columns = _columns; + vint childCount = Children().Count(); + for (vint i = 0; i < childCount; i++) + { + GuiCellComposition* cell = dynamic_cast(Children().Get(i)); + if (cell) + { + cell->OnTableRowsAndColumnsChanged(); + } + } + ConfigChanged.Execute(GuiEventArgs(this)); + UpdateCellBounds(); + return true; + } + + GuiCellComposition* GuiTableComposition::GetSitedCell(vint _row, vint _column) + { + return cellCompositions[GetSiteIndex(rows, columns, _row, _column)]; + } + + GuiCellOption GuiTableComposition::GetRowOption(vint _row) + { + return rowOptions[_row]; + } + + void GuiTableComposition::SetRowOption(vint _row, GuiCellOption option) + { + rowOptions[_row] = option; + UpdateCellBounds(); + ConfigChanged.Execute(GuiEventArgs(this)); + } + + GuiCellOption GuiTableComposition::GetColumnOption(vint _column) + { + return columnOptions[_column]; + } + + void GuiTableComposition::SetColumnOption(vint _column, GuiCellOption option) + { + columnOptions[_column] = option; + UpdateCellBounds(); + ConfigChanged.Execute(GuiEventArgs(this)); + } + + vint GuiTableComposition::GetCellPadding() + { + return cellPadding; + } + + void GuiTableComposition::SetCellPadding(vint value) + { + if (value < 0) value = 0; + cellPadding = value; + UpdateCellBounds(); + } + + bool GuiTableComposition::GetBorderVisible() + { + return borderVisible; + } + + void GuiTableComposition::SetBorderVisible(bool value) + { + if (borderVisible != value) + { + borderVisible = value; + UpdateCellBounds(); + } + } + + Rect GuiTableComposition::GetCellArea() + { + Rect bounds(Point(0, 0), GuiBoundsComposition::GetBounds().GetSize()); + vint borderThickness = borderVisible ? cellPadding : 0; + bounds.x1 += margin.left + internalMargin.left + borderThickness; + bounds.y1 += margin.top + internalMargin.top + borderThickness; + bounds.x2 -= margin.right + internalMargin.right + borderThickness; + bounds.y2 -= margin.bottom + internalMargin.bottom + borderThickness; + if (bounds.x2 < bounds.x1) bounds.x2 = bounds.x1; + if (bounds.y2 < bounds.y1) bounds.y2 = bounds.y1; + return bounds; + } + + void GuiTableComposition::UpdateCellBounds() + { + rowOffsets.Resize(rows); + rowSizes.Resize(rows); + columnOffsets.Resize(columns); + columnSizes.Resize(columns); + + vint rowTotal = (rows - 1) * cellPadding; + vint columnTotal = (columns - 1) * cellPadding; + vint rowTotalWithPercentage = rowTotal; + vint columnTotalWithPercentage = columnTotal; + + UpdateCellBoundsInternal( + rowSizes, + rowTotal, + rowTotalWithPercentage, + rowOptions, + &GuiTableComposition::rows, + &GuiTableComposition::columns, + &Y, + &RL, + &RS, + &First, + &Second, + 1 + ); + UpdateCellBoundsInternal( + columnSizes, + columnTotal, + columnTotalWithPercentage, + columnOptions, + &GuiTableComposition::columns, + &GuiTableComposition::rows, + &X, + &CL, + &CS, + &Second, + &First, + 1 + ); + + Rect area = GetCellArea(); + UpdateCellBoundsPercentages(rowSizes, rowTotal, area.Height(), rowOptions); + UpdateCellBoundsPercentages(columnSizes, columnTotal, area.Width(), columnOptions); + rowExtending = UpdateCellBoundsOffsets(rowOffsets, rowSizes, area.Height()); + columnExtending = UpdateCellBoundsOffsets(columnOffsets, columnSizes, area.Width()); + + for (vint i = 0; i < rows; i++) + { + for (vint j = 0; j < columns; j++) + { + vint index = GetSiteIndex(rows, columns, i, j); + cellBounds[index] = Rect(Point(columnOffsets[j], rowOffsets[i]), Size(columnSizes[j], rowSizes[i])); + } + } + + tableContentMinSize = Size(columnTotalWithPercentage, rowTotalWithPercentage); + InvokeOnCompositionStateChanged(); + } + + void GuiTableComposition::ForceCalculateSizeImmediately() + { + GuiBoundsComposition::ForceCalculateSizeImmediately(); + UpdateCellBounds(); + UpdateCellBounds(); + } + + Size GuiTableComposition::GetMinPreferredClientSize() + { + vint offset = (borderVisible ? 2 * cellPadding : 0); + return Size(tableContentMinSize.x + offset, tableContentMinSize.y + offset); + } + + Rect GuiTableComposition::GetBounds() + { + Rect cached = previousBounds; + Rect result = GuiBoundsComposition::GetBounds(); + + bool cellMinSizeModified = false; + SortedList cells; + FOREACH(GuiCellComposition*, cell, cellCompositions) + { + if (cell && !cells.Contains(cell)) + { + cells.Add(cell); + Size newSize = cell->GetPreferredBounds().GetSize(); + if (cell->lastPreferredSize != newSize) + { + cell->lastPreferredSize = newSize; + cellMinSizeModified = true; + } + } + } + + if (cached != result || cellMinSizeModified) + { + UpdateCellBounds(); + } + return result; + } + +/*********************************************************************** +GuiCellComposition +***********************************************************************/ + + void GuiCellComposition::ClearSitedCells(GuiTableComposition* table) + { + if (row != -1 && column != -1) + { + for (vint r = 0; r < rowSpan; r++) + { + for (vint c = 0; c < columnSpan; c++) + { + table->SetSitedCell(row + r, column + c, 0); + } + } + } + } + + void GuiCellComposition::SetSitedCells(GuiTableComposition* table) + { + for (vint r = 0; r < rowSpan; r++) + { + for (vint c = 0; c < columnSpan; c++) + { + table->SetSitedCell(row + r, column + c, this); + } + } + } + + void GuiCellComposition::ResetSiteInternal() + { + row = -1; + column = -1; + rowSpan = 1; + columnSpan = 1; + } + + bool GuiCellComposition::SetSiteInternal(vint _row, vint _column, vint _rowSpan, vint _columnSpan) + { + if (tableParent) + { + if (_row < 0 || _row >= tableParent->rows || _column < 0 || _column >= tableParent->columns) return false; + if (_rowSpan<1 || _row + _rowSpan>tableParent->rows || _columnSpan<1 || _column + _columnSpan>tableParent->columns) return false; + + for (vint r = 0; r < _rowSpan; r++) + { + for (vint c = 0; c < _columnSpan; c++) + { + GuiCellComposition* cell = tableParent->GetSitedCell(_row + r, _column + c); + if (cell && cell != this) + { + return false; + } + } + } + ClearSitedCells(tableParent); + } + + row = _row; + column = _column; + rowSpan = _rowSpan; + columnSpan = _columnSpan; + + if (tableParent) + { + SetSitedCells(tableParent); + } + return true; + } + + void GuiCellComposition::OnParentChanged(GuiGraphicsComposition* oldParent, GuiGraphicsComposition* newParent) + { + GuiGraphicsSite::OnParentChanged(oldParent, newParent); + if (tableParent) + { + ClearSitedCells(tableParent); + } + tableParent = dynamic_cast(newParent); + if (!tableParent || !SetSiteInternal(row, column, rowSpan, columnSpan)) + { + ResetSiteInternal(); + } + if (tableParent) + { + if (row != -1 && column != -1) + { + SetSiteInternal(row, column, rowSpan, columnSpan); + } + tableParent->UpdateCellBounds(); + } + } + + void GuiCellComposition::OnTableRowsAndColumnsChanged() + { + if(!SetSiteInternal(row, column, rowSpan, columnSpan)) + { + ResetSiteInternal(); + } + } + + GuiCellComposition::GuiCellComposition() + :row(-1) + ,column(-1) + ,rowSpan(1) + ,columnSpan(1) + ,tableParent(0) + { + SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + } + + GuiCellComposition::~GuiCellComposition() + { + } + + GuiTableComposition* GuiCellComposition::GetTableParent() + { + return tableParent; + } + + vint GuiCellComposition::GetRow() + { + return row; + } + + vint GuiCellComposition::GetRowSpan() + { + return rowSpan; + } + + vint GuiCellComposition::GetColumn() + { + return column; + } + + vint GuiCellComposition::GetColumnSpan() + { + return columnSpan; + } + + bool GuiCellComposition::SetSite(vint _row, vint _column, vint _rowSpan, vint _columnSpan) + { + if (!SetSiteInternal(_row, _column, _rowSpan, _columnSpan)) + { + return false; + } + + if (tableParent) + { + tableParent->UpdateCellBounds(); + } + return true; + } + + Rect GuiCellComposition::GetBounds() + { + Rect result; + if(tableParent && row!=-1 && column!=-1) + { + Rect bounds1, bounds2; + { + vint index=tableParent->GetSiteIndex(tableParent->rows, tableParent->columns, row, column); + bounds1=tableParent->cellBounds[index]; + } + { + vint index=tableParent->GetSiteIndex(tableParent->rows, tableParent->columns, row+rowSpan-1, column+columnSpan-1); + bounds2=tableParent->cellBounds[index]; + if(tableParent->GetMinSizeLimitation()==GuiGraphicsComposition::NoLimit) + { + if(row+rowSpan==tableParent->rows) + { + bounds2.y2+=tableParent->rowExtending; + } + if(column+columnSpan==tableParent->columns) + { + bounds2.x2+=tableParent->columnExtending; + } + } + } + vint offset = tableParent->borderVisible ? tableParent->cellPadding : 0; + result = Rect(bounds1.x1 + offset, bounds1.y1 + offset, bounds2.x2 + offset, bounds2.y2 + offset); + } + else + { + result = Rect(); + } + UpdatePreviousBounds(result); + return result; + } + +/*********************************************************************** +GuiTableSplitterCompositionBase +***********************************************************************/ + + void GuiTableSplitterCompositionBase::OnParentChanged(GuiGraphicsComposition* oldParent, GuiGraphicsComposition* newParent) + { + GuiGraphicsSite::OnParentChanged(oldParent, newParent); + tableParent = dynamic_cast(newParent); + } + + void GuiTableSplitterCompositionBase::OnLeftButtonDown(GuiGraphicsComposition* sender, GuiMouseEventArgs& arguments) + { + dragging = true; + draggingPoint = Point(arguments.x, arguments.y); + } + + void GuiTableSplitterCompositionBase::OnLeftButtonUp(GuiGraphicsComposition* sender, GuiMouseEventArgs& arguments) + { + dragging = false; + } + + void GuiTableSplitterCompositionBase::OnMouseMoveHelper( + vint cellsBefore, + vint GuiTableComposition::* cells, + collections::Array& cellSizes, + vint offset, + GuiCellOption(GuiTableComposition::*getOption)(vint), + void(GuiTableComposition::*setOption)(vint, GuiCellOption) + ) + { + if (dragging) + { + if (tableParent) + { + if (0 < cellsBefore && cellsBefore < tableParent->*cells) + { + auto o1 = (tableParent->*getOption)(cellsBefore - 1); + auto o2 = (tableParent->*getOption)(cellsBefore); + + vint indexStart = -1; + vint indexEnd = -1; + vint indexStep = -1; + vint max = 0; + + if (offset < 0) + { + indexStart = cellsBefore - 1; + indexEnd = -1; + indexStep = -1; + } + else if (offset > 0) + { + indexStart = cellsBefore; + indexEnd = tableParent->*cells; + indexStep = 1; + } + else + { + return; + } + + { + auto o = (tableParent->*getOption)(indexStart); + if (o.composeType == GuiCellOption::Absolute) + { + max = o.absolute - 1; + } + else + { + for (vint i = indexStart; i != indexEnd; i += indexStep) + { + o = (tableParent->*getOption)(i); + if (o.composeType == GuiCellOption::Absolute) + { + break; + } + else if (o.composeType == GuiCellOption::Percentage) + { + max += cellSizes[i] - 1; + } + } + } + + if (max <= 0) + { + return; + } + } + + if (offset < 0) + { + if (max < -offset) + { + offset = -max; + } + } + else + { + if (max < offset) + { + offset = max; + } + } + + if (o1.composeType == GuiCellOption::Absolute) + { + o1.absolute += offset; + (tableParent->*setOption)(cellsBefore - 1, o1); + } + if (o2.composeType == GuiCellOption::Absolute) + { + o2.absolute -= offset; + (tableParent->*setOption)(cellsBefore, o2); + } + tableParent->ForceCalculateSizeImmediately(); + } + } + } + } + + Rect GuiTableSplitterCompositionBase::GetBoundsHelper( + vint cellsBefore, + vint GuiTableComposition::* cells, + vint(Rect::* dimSize)()const, + collections::Array& cellOffsets, + vint Rect::* dimU1, + vint Rect::* dimU2, + vint Rect::* dimV1, + vint Rect::* dimV2 + ) + { + Rect result(0, 0, 0, 0); + if (tableParent) + { + if (0 < cellsBefore && cellsBefore < tableParent->*cells) + { + vint offset = tableParent->borderVisible ? tableParent->cellPadding : 0; + result.*dimU1 = offset; + result.*dimU2 = offset + (tableParent->GetCellArea().*dimSize)(); + result.*dimV1 = offset + cellOffsets[cellsBefore] - tableParent->cellPadding; + result.*dimV2 = (result.*dimV1) + tableParent->cellPadding; + } + } + UpdatePreviousBounds(result); + return result; + } + + GuiTableSplitterCompositionBase::GuiTableSplitterCompositionBase() + :tableParent(0) + , dragging(false) + { + SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::SizeNS)); + GetEventReceiver()->leftButtonDown.AttachMethod(this, &GuiTableSplitterCompositionBase::OnLeftButtonDown); + GetEventReceiver()->leftButtonUp.AttachMethod(this, &GuiTableSplitterCompositionBase::OnLeftButtonUp); + } + + GuiTableSplitterCompositionBase::~GuiTableSplitterCompositionBase() + { + } + + GuiTableComposition* GuiTableSplitterCompositionBase::GetTableParent() + { + return tableParent; + } + +/*********************************************************************** +GuiRowSplitterComposition +***********************************************************************/ + + void GuiRowSplitterComposition::OnMouseMove(GuiGraphicsComposition* sender, GuiMouseEventArgs& arguments) + { + OnMouseMoveHelper( + rowsToTheTop, + &GuiTableComposition::rows, + tableParent->rowSizes, + arguments.y - draggingPoint.y, + &GuiTableComposition::GetRowOption, + &GuiTableComposition::SetRowOption + ); + } + + GuiRowSplitterComposition::GuiRowSplitterComposition() + :rowsToTheTop(0) + { + SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::SizeNS)); + GetEventReceiver()->mouseMove.AttachMethod(this, &GuiRowSplitterComposition::OnMouseMove); + } + + GuiRowSplitterComposition::~GuiRowSplitterComposition() + { + } + + vint GuiRowSplitterComposition::GetRowsToTheTop() + { + return rowsToTheTop; + } + + void GuiRowSplitterComposition::SetRowsToTheTop(vint value) + { + rowsToTheTop = value; + InvokeOnCompositionStateChanged(); + } + + Rect GuiRowSplitterComposition::GetBounds() + { + return GetBoundsHelper( + rowsToTheTop, + &GuiTableComposition::rows, + &Rect::Width, + tableParent->rowOffsets, + &Rect::x1, + &Rect::x2, + &Rect::y1, + &Rect::y2 + ); + } + +/*********************************************************************** +GuiColumnSplitterComposition +***********************************************************************/ + + void GuiColumnSplitterComposition::OnMouseMove(GuiGraphicsComposition* sender, GuiMouseEventArgs& arguments) + { + OnMouseMoveHelper( + columnsToTheLeft, + &GuiTableComposition::columns, + tableParent->columnSizes, + arguments.x - draggingPoint.x, + &GuiTableComposition::GetColumnOption, + &GuiTableComposition::SetColumnOption + ); + } + + GuiColumnSplitterComposition::GuiColumnSplitterComposition() + :columnsToTheLeft(0) + { + SetAssociatedCursor(GetCurrentController()->ResourceService()->GetSystemCursor(INativeCursor::SizeWE)); + GetEventReceiver()->mouseMove.AttachMethod(this, &GuiColumnSplitterComposition::OnMouseMove); + } + + GuiColumnSplitterComposition::~GuiColumnSplitterComposition() + { + } + + vint GuiColumnSplitterComposition::GetColumnsToTheLeft() + { + return columnsToTheLeft; + } + + void GuiColumnSplitterComposition::SetColumnsToTheLeft(vint value) + { + columnsToTheLeft = value; + InvokeOnCompositionStateChanged(); + } + + Rect GuiColumnSplitterComposition::GetBounds() + { + return GetBoundsHelper( + columnsToTheLeft, + &GuiTableComposition::columns, + &Rect::Height, + tableParent->columnOffsets, + &Rect::y1, + &Rect::y2, + &Rect::x1, + &Rect::x2 + ); + } + } + } +} + +/*********************************************************************** +.\GRAPHICSELEMENT\GUIGRAPHICSDOCUMENTELEMENT.CPP +***********************************************************************/ + +namespace vl +{ + using namespace collections; + + namespace presentation + { + namespace elements + { + +/*********************************************************************** +SetPropertiesVisitor +***********************************************************************/ + + namespace visitors + { + class SetPropertiesVisitor : public Object, public DocumentRun::IVisitor + { + typedef GuiDocumentElement::GuiDocumentElementRenderer Renderer; + typedef DocumentModel::ResolvedStyle ResolvedStyle; + public: + vint start; + vint length; + vint selectionBegin; + vint selectionEnd; + List styles; + + DocumentModel* model; + Renderer* renderer; + Ptr cache; + IGuiGraphicsParagraph* paragraph; + + SetPropertiesVisitor(DocumentModel* _model, Renderer* _renderer, Ptr _cache, vint _selectionBegin, vint _selectionEnd) + :start(0) + ,length(0) + ,model(_model) + ,renderer(_renderer) + ,cache(_cache) + ,paragraph(_cache->graphicsParagraph.Obj()) + ,selectionBegin(_selectionBegin) + ,selectionEnd(_selectionEnd) + { + ResolvedStyle style; + style=model->GetStyle(DocumentModel::DefaultStyleName, style); + styles.Add(style); + } + + void VisitContainer(DocumentContainerRun* run) + { + FOREACH(Ptr, subRun, run->runs) + { + subRun->Accept(this); + } + } + + void ApplyStyle(vint start, vint length, const ResolvedStyle& style) + { + paragraph->SetFont(start, length, style.style.fontFamily); + paragraph->SetSize(start, length, style.style.size); + paragraph->SetStyle(start, length, + (IGuiGraphicsParagraph::TextStyle) + ( (style.style.bold?IGuiGraphicsParagraph::Bold:0) + | (style.style.italic?IGuiGraphicsParagraph::Italic:0) + | (style.style.underline?IGuiGraphicsParagraph::Underline:0) + | (style.style.strikeline?IGuiGraphicsParagraph::Strikeline:0) + )); + } + + void ApplyColor(vint start, vint length, const ResolvedStyle& style) + { + paragraph->SetColor(start, length, style.color); + paragraph->SetBackgroundColor(start, length, style.backgroundColor); + } + + void Visit(DocumentTextRun* run)override + { + length=run->GetRepresentationText().Length(); + if(length>0) + { + ResolvedStyle style=styles[styles.Count()-1]; + ApplyStyle(start, length, style); + ApplyColor(start, length, style); + + vint styleStart=start; + vint styleEnd=styleStart+length; + if(styleStartselectionBegin?styleStart:selectionBegin; + vint s3=selectionEndGetStyle(DocumentModel::SelectionStyleName, style); + ApplyColor(s2, s3-s2, selectionStyle); + } + } + } + start+=length; + } + + void Visit(DocumentStylePropertiesRun* run)override + { + ResolvedStyle style=styles[styles.Count()-1]; + style=model->GetStyle(run->style, style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count()-1); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + ResolvedStyle style=styles[styles.Count()-1]; + style=model->GetStyle(run->styleName, style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count()-1); + } + + void Visit(DocumentHyperlinkRun* run)override + { + ResolvedStyle style=styles[styles.Count()-1]; + style=model->GetStyle(run->styleName, style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count()-1); + } + + void Visit(DocumentImageRun* run)override + { + length=run->GetRepresentationText().Length(); + + Ptr element=GuiImageFrameElement::Create(); + element->SetImage(run->image, run->frameIndex); + element->SetStretch(true); + + IGuiGraphicsParagraph::InlineObjectProperties properties; + properties.size=run->size; + properties.baseline=run->baseline; + properties.breakCondition=IGuiGraphicsParagraph::Alone; + properties.backgroundImage = element; + + paragraph->SetInlineObject(start, length, properties); + + if(startGetStyle(DocumentModel::SelectionStyleName, style); + ApplyColor(start, length, selectionStyle); + } + start+=length; + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + length=run->GetRepresentationText().Length(); + + IGuiGraphicsParagraph::InlineObjectProperties properties; + properties.breakCondition=IGuiGraphicsParagraph::Alone; + + if (run->name != L"") + { + vint index = renderer->nameCallbackIdMap.Keys().IndexOf(run->name); + if (index != -1) + { + auto id = renderer->nameCallbackIdMap.Values()[index]; + index = cache->embeddedObjects.Keys().IndexOf(id); + if (index != -1) + { + auto eo = cache->embeddedObjects.Values()[index]; + if (eo->start == start) + { + properties.size = eo->size; + properties.callbackId = id; + } + } + } + else + { + auto eo = MakePtr(); + eo->name = run->name; + eo->size = Size(0, 0); + eo->start = start; + + vint id = -1; + vint count = renderer->freeCallbackIds.Count(); + if (count > 0) + { + id = renderer->freeCallbackIds[count - 1]; + renderer->freeCallbackIds.RemoveAt(count - 1); + } + else + { + id = renderer->usedCallbackIds++; + } + + renderer->nameCallbackIdMap.Add(eo->name, id); + cache->embeddedObjects.Add(id, eo); + properties.callbackId = id; + } + } + + paragraph->SetInlineObject(start, length, properties); + + if(startGetStyle(DocumentModel::SelectionStyleName, style); + ApplyColor(start, length, selectionStyle); + } + start+=length; + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + + static vint SetProperty(DocumentModel* model, Renderer* renderer, Ptr cache, Ptr run, vint selectionBegin, vint selectionEnd) + { + SetPropertiesVisitor visitor(model, renderer, cache, selectionBegin, selectionEnd); + run->Accept(&visitor); + return visitor.length; + } + }; + } + using namespace visitors; + +/*********************************************************************** +GuiDocumentElement::GuiDocumentElementRenderer +***********************************************************************/ + + Size GuiDocumentElement::GuiDocumentElementRenderer::OnRenderInlineObject(vint callbackId, Rect location) + { + if (element->callback) + { + auto cache = paragraphCaches[renderingParagraph]; + auto relativeLocation = Rect(Point(location.x1 + renderingParagraphOffset.x, location.y1 + renderingParagraphOffset.y), location.GetSize()); + auto eo = cache->embeddedObjects[callbackId]; + auto size = element->callback->OnRenderEmbeddedObject(eo->name, relativeLocation); + eo->resized = eo->size != size; + eo->size = size; + return eo->size; + } + else + { + return Size(); + } + } + + void GuiDocumentElement::GuiDocumentElementRenderer::InitializeInternal() + { + } + + void GuiDocumentElement::GuiDocumentElementRenderer::FinalizeInternal() + { + } + + void GuiDocumentElement::GuiDocumentElementRenderer::RenderTargetChangedInternal(IGuiGraphicsRenderTarget* oldRenderTarget, IGuiGraphicsRenderTarget* newRenderTarget) + { + for(vint i=0;igraphicsParagraph=0; + } + } + } + + Ptr GuiDocumentElement::GuiDocumentElementRenderer::EnsureAndGetCache(vint paragraphIndex, bool createParagraph) + { + if(paragraphIndex<0 || paragraphIndex>=paragraphCaches.Count()) return 0; + Ptr paragraph=element->document->paragraphs[paragraphIndex]; + Ptr cache=paragraphCaches[paragraphIndex]; + if(!cache) + { + cache=new ParagraphCache; + cache->fullText=paragraph->GetText(false); + paragraphCaches[paragraphIndex]=cache; + } + + if(createParagraph) + { + if(!cache->graphicsParagraph) + { + cache->graphicsParagraph=layoutProvider->CreateParagraph(cache->fullText, renderTarget, this); + cache->graphicsParagraph->SetParagraphAlignment(paragraph->alignment ? paragraph->alignment.Value() : Alignment::Left); + SetPropertiesVisitor::SetProperty(element->document.Obj(), this, cache, paragraph, cache->selectionBegin, cache->selectionEnd); + } + if(cache->graphicsParagraph->GetMaxWidth()!=lastMaxWidth) + { + cache->graphicsParagraph->SetMaxWidth(lastMaxWidth); + } + + vint paragraphHeight=paragraphHeights[paragraphIndex]; + vint height=cache->graphicsParagraph->GetHeight(); + if(paragraphHeight!=height) + { + cachedTotalHeight+=height-paragraphHeight; + paragraphHeight=height; + paragraphHeights[paragraphIndex]=paragraphHeight; + minSize=Size(0, cachedTotalHeight); + } + } + + return cache; + } + + bool GuiDocumentElement::GuiDocumentElementRenderer::GetParagraphIndexFromPoint(Point point, vint& top, vint& index) + { + vint y=0; + for(vint i=0;iGetLayoutProvider()) + ,lastCaret(-1, -1) + ,lastCaretFrontSide(false) + { + } + + void GuiDocumentElement::GuiDocumentElementRenderer::Render(Rect bounds) + { + if (element->callback) + { + element->callback->OnStartRender(); + } + renderTarget->PushClipper(bounds); + if(!renderTarget->IsClipperCoverWholeTarget()) + { + vint maxWidth=bounds.Width(); + Rect clipper=renderTarget->GetClipper(); + vint cx=bounds.Left(); + vint cy=bounds.Top(); + vint y1=clipper.Top()-bounds.Top(); + vint y2=y1+clipper.Height(); + vint y=0; + + lastMaxWidth=maxWidth; + + for(vint i=0;i=y2) + { + break; + } + else + { + Ptr paragraph=element->document->paragraphs[i]; + Ptr cache=paragraphCaches[i]; + bool created=cache && cache->graphicsParagraph; + cache=EnsureAndGetCache(i, true); + if(!created && i==lastCaret.row && element->caretVisible) + { + cache->graphicsParagraph->OpenCaret(lastCaret.column, lastCaretColor, lastCaretFrontSide); + } + + paragraphHeight=cache->graphicsParagraph->GetHeight(); + + renderingParagraph = i; + renderingParagraphOffset = Point(cx - bounds.x1, cy + y - bounds.y1); + cache->graphicsParagraph->Render(Rect(Point(cx, cy+y), Size(maxWidth, paragraphHeight))); + renderingParagraph = -1; + + bool resized = false; + for (vint j = 0; j < cache->embeddedObjects.Count(); j++) + { + auto eo = cache->embeddedObjects.Values()[j]; + if (eo->resized) + { + eo->resized = false; + resized = true; + } + } + + if (resized) + { + cache->graphicsParagraph = 0; + } + } + + y+=paragraphHeight+paragraphDistance; + } + } + renderTarget->PopClipper(); + if (element->callback) + { + element->callback->OnFinishRender(); + } + } + + void GuiDocumentElement::GuiDocumentElementRenderer::OnElementStateChanged() + { + if (element->document && element->document->paragraphs.Count() > 0) + { + vint defaultSize = GetCurrentController()->ResourceService()->GetDefaultFont().size; + paragraphDistance = defaultSize; + vint defaultHeight = defaultSize; + + paragraphCaches.Resize(element->document->paragraphs.Count()); + paragraphHeights.Resize(element->document->paragraphs.Count()); + + for (vint i = 0; i < paragraphCaches.Count(); i++) + { + paragraphCaches[i] = 0; + } + for (vint i = 0; i < paragraphHeights.Count(); i++) + { + paragraphHeights[i] = defaultHeight; + } + + cachedTotalHeight = paragraphHeights.Count() * (defaultHeight + paragraphDistance); + if (paragraphHeights.Count()>0) + { + cachedTotalHeight -= paragraphDistance; + } + minSize = Size(0, cachedTotalHeight); + } + else + { + paragraphCaches.Resize(0); + paragraphHeights.Resize(0); + cachedTotalHeight = 0; + minSize = Size(0, 0); + } + + nameCallbackIdMap.Clear(); + freeCallbackIds.Clear(); + usedCallbackIds = 0; + } + + void GuiDocumentElement::GuiDocumentElementRenderer::NotifyParagraphUpdated(vint index, vint oldCount, vint newCount, bool updatedText) + { + if (0 <= index && index < paragraphCaches.Count() && 0 <= oldCount && index + oldCount <= paragraphCaches.Count() && 0 <= newCount) + { + vint paragraphCount = element->document->paragraphs.Count(); + CHECK_ERROR(updatedText || oldCount == newCount, L"GuiDocumentlement::GuiDocumentElementRenderer::NotifyParagraphUpdated(vint, vint, vint, bool)#Illegal values of oldCount and newCount."); + CHECK_ERROR(paragraphCount - paragraphCaches.Count() == newCount - oldCount, L"GuiDocumentElement::GuiDocumentElementRenderer::NotifyParagraphUpdated(vint, vint, vint, bool)#Illegal values of oldCount and newCount."); + + ParagraphCacheArray oldCaches; + CopyFrom(oldCaches, paragraphCaches); + paragraphCaches.Resize(paragraphCount); + + ParagraphHeightArray oldHeights; + CopyFrom(oldHeights, paragraphHeights); + paragraphHeights.Resize(paragraphCount); + + vint defaultHeight = GetCurrentController()->ResourceService()->GetDefaultFont().size; + cachedTotalHeight = 0; + + for (vint i = 0; i < paragraphCount; i++) + { + if (i < index) + { + paragraphCaches[i] = oldCaches[i]; + paragraphHeights[i] = oldHeights[i]; + } + else if (i < index + newCount) + { + paragraphCaches[i] = 0; + paragraphHeights[i] = defaultHeight; + if (!updatedText && i < index + oldCount) + { + auto cache = oldCaches[i]; + if(cache) + { + cache->graphicsParagraph = 0; + } + paragraphCaches[i] = cache; + paragraphHeights[i] = oldHeights[i]; + } + } + else + { + paragraphCaches[i] = oldCaches[i - (newCount - oldCount)]; + paragraphHeights[i] = oldHeights[i - (newCount - oldCount)]; + } + cachedTotalHeight += paragraphHeights[i] + paragraphDistance; + } + if (paragraphCount > 0) + { + cachedTotalHeight -= paragraphDistance; + } + + if (updatedText) + { + vint count = oldCount < newCount ? oldCount : newCount; + for (vint i = 0; i < count; i++) + { + if (auto cache = oldCaches[index + i]) + { + for (vint j = 0; j < cache->embeddedObjects.Count(); j++) + { + auto id = cache->embeddedObjects.Keys()[j]; + auto name = cache->embeddedObjects.Values()[j]->name; + nameCallbackIdMap.Remove(name); + freeCallbackIds.Add(id); + } + } + } + } + } + } + + Ptr GuiDocumentElement::GuiDocumentElementRenderer::GetHyperlinkFromPoint(Point point) + { + vint top=0; + vint index=-1; + if(GetParagraphIndexFromPoint(point, top, index)) + { + Ptr cache=EnsureAndGetCache(index, true); + Point paragraphPoint(point.x, point.y-top); + + vint start=-1; + vint length=0; + if(cache->graphicsParagraph->GetInlineObjectFromPoint(paragraphPoint, start, length)) + { + return element->document->GetHyperlink(index, start, start+length); + } + + vint caret=cache->graphicsParagraph->GetCaretFromPoint(paragraphPoint); + return element->document->GetHyperlink(index, caret, caret); + } + return 0; + } + + void GuiDocumentElement::GuiDocumentElementRenderer::OpenCaret(TextPos caret, Color color, bool frontSide) + { + CloseCaret(caret); + lastCaret=caret; + lastCaretColor=color; + lastCaretFrontSide=frontSide; + + Ptr cache=paragraphCaches[lastCaret.row]; + if(cache && cache->graphicsParagraph) + { + cache->graphicsParagraph->OpenCaret(lastCaret.column, lastCaretColor, lastCaretFrontSide); + } + } + + void GuiDocumentElement::GuiDocumentElementRenderer::CloseCaret(TextPos caret) + { + if(lastCaret!=TextPos(-1, -1)) + { + if(0<=lastCaret.row && lastCaret.row cache=paragraphCaches[lastCaret.row]; + if(cache && cache->graphicsParagraph) + { + cache->graphicsParagraph->CloseCaret(); + } + } + } + lastCaret=caret; + } + + void GuiDocumentElement::GuiDocumentElementRenderer::SetSelection(TextPos begin, TextPos end) + { + if(begin>end) + { + TextPos t=begin; + begin=end; + end=t; + } + if(begin==end) + { + begin=TextPos(-1, -1); + end=TextPos(-1, -1); + } + + for(vint i=0;i cache=EnsureAndGetCache(i, false); + vint newBegin=i==begin.row?begin.column:0; + vint newEnd=i==end.row?end.column:cache->fullText.Length(); + + if(cache->selectionBegin!=newBegin || cache->selectionEnd!=newEnd) + { + cache->selectionBegin=newBegin; + cache->selectionEnd=newEnd; + NotifyParagraphUpdated(i, 1, 1, false); + } + } + else + { + Ptr cache=paragraphCaches[i]; + if(cache) + { + if(cache->selectionBegin!=-1 || cache->selectionEnd!=-1) + { + cache->selectionBegin=-1; + cache->selectionEnd=-1; + NotifyParagraphUpdated(i, 1, 1, false); + } + } + } + } + } + + TextPos GuiDocumentElement::GuiDocumentElementRenderer::CalculateCaret(TextPos comparingCaret, IGuiGraphicsParagraph::CaretRelativePosition position, bool& preferFrontSide) + { + Ptr cache=EnsureAndGetCache(comparingCaret.row, true); + if(cache) + { + switch(position) + { + case IGuiGraphicsParagraph::CaretFirst: + { + preferFrontSide=false; + vint caret=cache->graphicsParagraph->GetCaret(0, IGuiGraphicsParagraph::CaretFirst, preferFrontSide); + return TextPos(comparingCaret.row, caret); + } + case IGuiGraphicsParagraph::CaretLast: + { + preferFrontSide=true; + vint caret=cache->graphicsParagraph->GetCaret(0, IGuiGraphicsParagraph::CaretLast, preferFrontSide); + return TextPos(comparingCaret.row, caret); + } + case IGuiGraphicsParagraph::CaretLineFirst: + { + preferFrontSide=false; + vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretLineFirst, preferFrontSide); + return TextPos(comparingCaret.row, caret); + } + case IGuiGraphicsParagraph::CaretLineLast: + { + preferFrontSide=true; + vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretLineLast, preferFrontSide); + return TextPos(comparingCaret.row, caret); + } + case IGuiGraphicsParagraph::CaretMoveUp: + { + vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretMoveUp, preferFrontSide); + if(caret==comparingCaret.column && comparingCaret.row>0) + { + Rect caretBounds=cache->graphicsParagraph->GetCaretBounds(comparingCaret.column, preferFrontSide); + Ptr anotherCache=EnsureAndGetCache(comparingCaret.row-1, true); + vint height=anotherCache->graphicsParagraph->GetHeight(); + caret=anotherCache->graphicsParagraph->GetCaretFromPoint(Point(caretBounds.x1, height)); + return TextPos(comparingCaret.row-1, caret); + } + else + { + return TextPos(comparingCaret.row, caret); + } + } + case IGuiGraphicsParagraph::CaretMoveDown: + { + vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretMoveDown, preferFrontSide); + if(caret==comparingCaret.column && comparingCaret.rowgraphicsParagraph->GetCaretBounds(comparingCaret.column, preferFrontSide); + Ptr anotherCache=EnsureAndGetCache(comparingCaret.row+1, true); + caret=anotherCache->graphicsParagraph->GetCaretFromPoint(Point(caretBounds.x1, 0)); + return TextPos(comparingCaret.row+1, caret); + } + else + { + return TextPos(comparingCaret.row, caret); + } + } + case IGuiGraphicsParagraph::CaretMoveLeft: + { + preferFrontSide=false; + vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretMoveLeft, preferFrontSide); + if(caret==comparingCaret.column && comparingCaret.row>0) + { + Ptr anotherCache=EnsureAndGetCache(comparingCaret.row-1, true); + caret=anotherCache->graphicsParagraph->GetCaret(0, IGuiGraphicsParagraph::CaretLast, preferFrontSide); + return TextPos(comparingCaret.row-1, caret); + } + else + { + return TextPos(comparingCaret.row, caret); + } + } + case IGuiGraphicsParagraph::CaretMoveRight: + { + preferFrontSide=true; + vint caret=cache->graphicsParagraph->GetCaret(comparingCaret.column, IGuiGraphicsParagraph::CaretMoveRight, preferFrontSide); + if(caret==comparingCaret.column && comparingCaret.row anotherCache=EnsureAndGetCache(comparingCaret.row+1, true); + caret=anotherCache->graphicsParagraph->GetCaret(0, IGuiGraphicsParagraph::CaretFirst, preferFrontSide); + return TextPos(comparingCaret.row+1, caret); + } + else + { + return TextPos(comparingCaret.row, caret); + } + } + } + } + return comparingCaret; + } + + TextPos GuiDocumentElement::GuiDocumentElementRenderer::CalculateCaretFromPoint(Point point) + { + vint top=0; + vint index=-1; + if(GetParagraphIndexFromPoint(point, top, index)) + { + Ptr cache=EnsureAndGetCache(index, true); + Point paragraphPoint(point.x, point.y-top); + vint caret=cache->graphicsParagraph->GetCaretFromPoint(paragraphPoint); + return TextPos(index, caret); + } + return TextPos(-1, -1); + } + + Rect GuiDocumentElement::GuiDocumentElementRenderer::GetCaretBounds(TextPos caret, bool frontSide) + { + Ptr cache=EnsureAndGetCache(caret.row, true); + if(cache) + { + Rect bounds=cache->graphicsParagraph->GetCaretBounds(caret.column, frontSide); + if(bounds!=Rect()) + { + vint y=0; + for(vint i=0;i(); + if (elementRenderer) + { + elementRenderer->SetSelection(caretBegin, caretEnd); + if (caretVisible) + { + elementRenderer->OpenCaret(caretEnd, caretColor, caretFrontSide); + } + else + { + elementRenderer->CloseCaret(caretEnd); + } + InvokeOnCompositionStateChanged(); + } + } + + GuiDocumentElement::GuiDocumentElement() + :caretVisible(false) + ,caretFrontSide(false) + { + } + + GuiDocumentElement::ICallback* GuiDocumentElement::GetCallback() + { + return callback; + } + + void GuiDocumentElement::SetCallback(ICallback* value) + { + callback = value; + } + + Ptr GuiDocumentElement::GetDocument() + { + return document; + } + + void GuiDocumentElement::SetDocument(Ptr value) + { + document=value; + InvokeOnElementStateChanged(); + SetCaret(TextPos(), TextPos(), false); + } + + TextPos GuiDocumentElement::GetCaretBegin() + { + return caretBegin; + } + + TextPos GuiDocumentElement::GetCaretEnd() + { + return caretEnd; + } + + bool GuiDocumentElement::IsCaretEndPreferFrontSide() + { + return caretFrontSide; + } + + void GuiDocumentElement::SetCaret(TextPos begin, TextPos end, bool frontSide) + { + caretBegin=begin; + caretEnd=end; + if(caretBegincaretEnd) + { + caretFrontSide=false; + } + else + { + caretFrontSide=frontSide; + } + UpdateCaret(); + } + + bool GuiDocumentElement::GetCaretVisible() + { + return caretVisible; + } + + void GuiDocumentElement::SetCaretVisible(bool value) + { + caretVisible=value; + UpdateCaret(); + } + + Color GuiDocumentElement::GetCaretColor() + { + return caretColor; + } + + void GuiDocumentElement::SetCaretColor(Color value) + { + caretColor=value; + UpdateCaret(); + } + + TextPos GuiDocumentElement::CalculateCaret(TextPos comparingCaret, IGuiGraphicsParagraph::CaretRelativePosition position, bool& preferFrontSide) + { + if (auto elementRenderer = renderer.Cast()) + { + TextPos caret=elementRenderer->CalculateCaret(comparingCaret, position, preferFrontSide); + return caret.column==-1?comparingCaret:caret; + } + else + { + return comparingCaret; + } + } + + TextPos GuiDocumentElement::CalculateCaretFromPoint(Point point) + { + if (auto elementRenderer = renderer.Cast()) + { + return elementRenderer->CalculateCaretFromPoint(point); + } + else + { + return TextPos(0, 0); + } + } + + Rect GuiDocumentElement::GetCaretBounds(TextPos caret, bool frontSide) + { + if (auto elementRenderer = renderer.Cast()) + { + return elementRenderer->GetCaretBounds(caret, frontSide); + } + else + { + return Rect(); + } + } + + void GuiDocumentElement::NotifyParagraphUpdated(vint index, vint oldCount, vint newCount, bool updatedText) + { + if (auto elementRenderer = renderer.Cast()) + { + elementRenderer->NotifyParagraphUpdated(index, oldCount, newCount, updatedText); + InvokeOnCompositionStateChanged(); + } + } + + void GuiDocumentElement::EditRun(TextPos begin, TextPos end, Ptr model, bool copy) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + + vint newRows = document->EditRun(begin, end, model, copy); + if (newRows != -1) + { + elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, newRows, true); + } + InvokeOnCompositionStateChanged(); + } + } + + void GuiDocumentElement::EditText(TextPos begin, TextPos end, bool frontSide, const collections::Array& text) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + + vint newRows = document->EditText(begin, end, frontSide, text); + if (newRows != -1) + { + elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, newRows, true); + } + InvokeOnCompositionStateChanged(); + } + } + + void GuiDocumentElement::EditStyle(TextPos begin, TextPos end, Ptr style) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + + if (document->EditStyle(begin, end, style)) + { + elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); + } + InvokeOnCompositionStateChanged(); + } + } + + void GuiDocumentElement::EditImage(TextPos begin, TextPos end, Ptr image) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + + if (document->EditImage(begin, end, image)) + { + elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, 1, true); + } + InvokeOnCompositionStateChanged(); + } + } + + void GuiDocumentElement::EditHyperlink(vint paragraphIndex, vint begin, vint end, const WString& reference, const WString& normalStyleName, const WString& activeStyleName) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + vint temp = begin; + begin = end; + end = temp; + } + + if (document->EditHyperlink(paragraphIndex, begin, end, reference, normalStyleName, activeStyleName)) + { + elementRenderer->NotifyParagraphUpdated(paragraphIndex, 1, 1, false); + } + InvokeOnCompositionStateChanged(); + } + } + + void GuiDocumentElement::RemoveHyperlink(vint paragraphIndex, vint begin, vint end) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + vint temp = begin; + begin = end; + end = temp; + } + + if (document->RemoveHyperlink(paragraphIndex, begin, end)) + { + elementRenderer->NotifyParagraphUpdated(paragraphIndex, 1, 1, false); + } + InvokeOnCompositionStateChanged(); + } + } + + void GuiDocumentElement::EditStyleName(TextPos begin, TextPos end, const WString& styleName) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + + if (document->EditStyleName(begin, end, styleName)) + { + elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); + } + InvokeOnCompositionStateChanged(); + } + } + + void GuiDocumentElement::RemoveStyleName(TextPos begin, TextPos end) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + + if (document->RemoveStyleName(begin, end)) + { + elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); + } + InvokeOnCompositionStateChanged(); + } + } + + void GuiDocumentElement::RenameStyle(const WString& oldStyleName, const WString& newStyleName) + { + if (auto elementRenderer = renderer.Cast()) + { + document->RenameStyle(oldStyleName, newStyleName); + } + } + + void GuiDocumentElement::ClearStyle(TextPos begin, TextPos end) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + + if (document->ClearStyle(begin, end)) + { + elementRenderer->NotifyParagraphUpdated(begin.row, end.row - begin.row + 1, end.row - begin.row + 1, false); + } + InvokeOnCompositionStateChanged(); + } + } + + Ptr GuiDocumentElement::SummarizeStyle(TextPos begin, TextPos end) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + + return document->SummarizeStyle(begin, end); + } + return nullptr; + } + + Nullable GuiDocumentElement::SummarizeStyleName(TextPos begin, TextPos end) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + + return document->SummarizeStyleName(begin, end); + } + return {}; + } + + void GuiDocumentElement::SetParagraphAlignment(TextPos begin, TextPos end, const collections::Array>& alignments) + { + if (auto elementRenderer = renderer.Cast()) + { + vint first = begin.row; + vint last = end.row; + if (first > last) + { + vint temp = first; + first = last; + last = temp; + } + + if (0 <= first && first < document->paragraphs.Count() && 0 <= last && last < document->paragraphs.Count() && last - first + 1 == alignments.Count()) + { + for (vint i = first; i <= last; i++) + { + document->paragraphs[i]->alignment = alignments[i - first]; + } + elementRenderer->NotifyParagraphUpdated(first, alignments.Count(), alignments.Count(), false); + } + InvokeOnCompositionStateChanged(); + } + } + + Nullable GuiDocumentElement::SummarizeParagraphAlignment(TextPos begin, TextPos end) + { + if (auto elementRenderer = renderer.Cast()) + { + if (begin > end) + { + TextPos temp = begin; + begin = end; + end = temp; + } + + return document->SummarizeParagraphAlignment(begin, end); + } + return {}; + } + + Ptr GuiDocumentElement::GetHyperlinkFromPoint(Point point) + { + if (auto elementRenderer = renderer.Cast()) + { + return elementRenderer->GetHyperlinkFromPoint(point); + } + return nullptr; + } + } + } +} + +/*********************************************************************** +.\GRAPHICSELEMENT\GUIGRAPHICSELEMENT.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace elements + { + using namespace collections; + +/*********************************************************************** +GuiSolidBorderElement +***********************************************************************/ + + GuiSolidBorderElement::GuiSolidBorderElement() + :color(0, 0, 0) + { + } + + Color GuiSolidBorderElement::GetColor() + { + return color; + } + + void GuiSolidBorderElement::SetColor(Color value) + { + if(color!=value) + { + color=value; + InvokeOnElementStateChanged(); + } + } + + ElementShape GuiSolidBorderElement::GetShape() + { + return shape; + } + + void GuiSolidBorderElement::SetShape(ElementShape value) + { + shape=value; + } + +/*********************************************************************** +Gui3DBorderElement +***********************************************************************/ + + Gui3DBorderElement::Gui3DBorderElement() + { + } + + Color Gui3DBorderElement::GetColor1() + { + return color1; + } + + void Gui3DBorderElement::SetColor1(Color value) + { + SetColors(value, color2); + } + + Color Gui3DBorderElement::GetColor2() + { + return color2; + } + + void Gui3DBorderElement::SetColor2(Color value) + { + SetColors(color1, value); + } + + void Gui3DBorderElement::SetColors(Color value1, Color value2) + { + if(color1!=value1 || color2!=value2) + { + color1=value1; + color2=value2; + InvokeOnElementStateChanged(); + } + } + +/*********************************************************************** +Gui3DSplitterElement +***********************************************************************/ + + Gui3DSplitterElement::Gui3DSplitterElement() + :direction(Horizontal) + { + } + + Color Gui3DSplitterElement::GetColor1() + { + return color1; + } + + void Gui3DSplitterElement::SetColor1(Color value) + { + SetColors(value, color2); + } + + Color Gui3DSplitterElement::GetColor2() + { + return color2; + } + + void Gui3DSplitterElement::SetColor2(Color value) + { + SetColors(color1, value); + } + + void Gui3DSplitterElement::SetColors(Color value1, Color value2) + { + if(color1!=value1 || color2!=value2) + { + color1=value1; + color2=value2; + InvokeOnElementStateChanged(); + } + } + + Gui3DSplitterElement::Direction Gui3DSplitterElement::GetDirection() + { + return direction; + } + + void Gui3DSplitterElement::SetDirection(Direction value) + { + if(direction!=value) + { + direction=value; + InvokeOnElementStateChanged(); + } + } + +/*********************************************************************** +GuiSolidBackgroundElement +***********************************************************************/ + + GuiSolidBackgroundElement::GuiSolidBackgroundElement() + :color(255, 255, 255) + { + } + + Color GuiSolidBackgroundElement::GetColor() + { + return color; + } + + void GuiSolidBackgroundElement::SetColor(Color value) + { + if(color!=value) + { + color=value; + InvokeOnElementStateChanged(); + } + } + + ElementShape GuiSolidBackgroundElement::GetShape() + { + return shape; + } + + void GuiSolidBackgroundElement::SetShape(ElementShape value) + { + shape=value; + } + +/*********************************************************************** +GuiGradientBackgroundElement +***********************************************************************/ + + GuiGradientBackgroundElement::GuiGradientBackgroundElement() + :direction(Horizontal) + { + } + + Color GuiGradientBackgroundElement::GetColor1() + { + return color1; + } + + void GuiGradientBackgroundElement::SetColor1(Color value) + { + SetColors(value, color2); + } + + Color GuiGradientBackgroundElement::GetColor2() + { + return color2; + } + + void GuiGradientBackgroundElement::SetColor2(Color value) + { + SetColors(color1, value); + } + + void GuiGradientBackgroundElement::SetColors(Color value1, Color value2) + { + if(color1!=value1 || color2!=value2) + { + color1=value1; + color2=value2; + InvokeOnElementStateChanged(); + } + } + + GuiGradientBackgroundElement::Direction GuiGradientBackgroundElement::GetDirection() + { + return direction; + } + + void GuiGradientBackgroundElement::SetDirection(Direction value) + { + if(direction!=value) + { + direction=value; + InvokeOnElementStateChanged(); + } + } + + ElementShape GuiGradientBackgroundElement::GetShape() + { + return shape; + } + + void GuiGradientBackgroundElement::SetShape(ElementShape value) + { + shape=value; + } + +/*********************************************************************** +GuiInnerShadowElement +***********************************************************************/ + + GuiInnerShadowElement::GuiInnerShadowElement() + { + } + + Color GuiInnerShadowElement::GetColor() + { + return color; + } + + void GuiInnerShadowElement::SetColor(Color value) + { + if (color != value) + { + color = value; + InvokeOnElementStateChanged(); + } + } + + vint GuiInnerShadowElement::GetThickness() + { + return thickness; + } + + void GuiInnerShadowElement::SetThickness(vint value) + { + if (thickness != value) + { + thickness = value; + InvokeOnElementStateChanged(); + } + } + +/*********************************************************************** +GuiSolidLabelElement +***********************************************************************/ + + GuiSolidLabelElement::GuiSolidLabelElement() + :color(0, 0, 0) + ,hAlignment(Alignment::Left) + ,vAlignment(Alignment::Top) + ,wrapLine(false) + ,ellipse(false) + ,multiline(false) + ,wrapLineHeightCalculation(false) + { + fontProperties.fontFamily=L"Lucida Console"; + fontProperties.size=12; + } + + Color GuiSolidLabelElement::GetColor() + { + return color; + } + + void GuiSolidLabelElement::SetColor(Color value) + { + if(color!=value) + { + color=value; + InvokeOnElementStateChanged(); + } + } + + const FontProperties& GuiSolidLabelElement::GetFont() + { + return fontProperties; + } + + void GuiSolidLabelElement::SetFont(const FontProperties& value) + { + if(fontProperties!=value) + { + fontProperties=value; + InvokeOnElementStateChanged(); + } + } + + const WString& GuiSolidLabelElement::GetText() + { + return text; + } + + void GuiSolidLabelElement::SetText(const WString& value) + { + if(text!=value) + { + text=value; + InvokeOnElementStateChanged(); + } + } + + Alignment GuiSolidLabelElement::GetHorizontalAlignment() + { + return hAlignment; + } + + Alignment GuiSolidLabelElement::GetVerticalAlignment() + { + return vAlignment; + } + + void GuiSolidLabelElement::SetHorizontalAlignment(Alignment value) + { + SetAlignments(value, vAlignment); + } + + void GuiSolidLabelElement::SetVerticalAlignment(Alignment value) + { + SetAlignments(hAlignment, value); + } + + void GuiSolidLabelElement::SetAlignments(Alignment horizontal, Alignment vertical) + { + if(hAlignment!=horizontal || vAlignment!=vertical) + { + hAlignment=horizontal; + vAlignment=vertical; + InvokeOnElementStateChanged(); + } + } + + bool GuiSolidLabelElement::GetWrapLine() + { + return wrapLine; + } + + void GuiSolidLabelElement::SetWrapLine(bool value) + { + if(wrapLine!=value) + { + wrapLine=value; + InvokeOnElementStateChanged(); + } + } + + bool GuiSolidLabelElement::GetEllipse() + { + return ellipse; + } + + void GuiSolidLabelElement::SetEllipse(bool value) + { + if(ellipse!=value) + { + ellipse=value; + InvokeOnElementStateChanged(); + } + } + + bool GuiSolidLabelElement::GetMultiline() + { + return multiline; + } + + void GuiSolidLabelElement::SetMultiline(bool value) + { + if(multiline!=value) + { + multiline=value; + InvokeOnElementStateChanged(); + } + } + + bool GuiSolidLabelElement::GetWrapLineHeightCalculation() + { + return wrapLineHeightCalculation; + } + + void GuiSolidLabelElement::SetWrapLineHeightCalculation(bool value) + { + if(wrapLineHeightCalculation!=value) + { + wrapLineHeightCalculation=value; + InvokeOnElementStateChanged(); + } + } + +/*********************************************************************** +GuiImageFrameElement +***********************************************************************/ + + GuiImageFrameElement::GuiImageFrameElement() + :frameIndex(0) + ,hAlignment(Alignment::Left) + ,vAlignment(Alignment::Top) + ,stretch(false) + ,enabled(true) + { + } + + Ptr GuiImageFrameElement::GetImage() + { + return image; + } + + vint GuiImageFrameElement::GetFrameIndex() + { + return frameIndex; + } + + void GuiImageFrameElement::SetImage(Ptr value) + { + SetImage(value, frameIndex); + } + + void GuiImageFrameElement::SetFrameIndex(vint value) + { + SetImage(image, value); + } + + void GuiImageFrameElement::SetImage(Ptr _image, vint _frameIndex) + { + if(image!=_image || frameIndex!=_frameIndex) + { + if(!_image) + { + image=0; + frameIndex=0; + } + else if(0<=_frameIndex && _frameIndex<_image->GetFrameCount()) + { + image=_image; + frameIndex=_frameIndex; + } + InvokeOnElementStateChanged(); + } + } + + Alignment GuiImageFrameElement::GetHorizontalAlignment() + { + return hAlignment; + } + + Alignment GuiImageFrameElement::GetVerticalAlignment() + { + return vAlignment; + } + + void GuiImageFrameElement::SetHorizontalAlignment(Alignment value) + { + SetAlignments(value, vAlignment); + } + + void GuiImageFrameElement::SetVerticalAlignment(Alignment value) + { + SetAlignments(hAlignment, value); + } + + void GuiImageFrameElement::SetAlignments(Alignment horizontal, Alignment vertical) + { + if(hAlignment!=horizontal || vAlignment!=vertical) + { + hAlignment=horizontal; + vAlignment=vertical; + InvokeOnElementStateChanged(); + } + } + + bool GuiImageFrameElement::GetStretch() + { + return stretch; + } + + void GuiImageFrameElement::SetStretch(bool value) + { + if(stretch!=value) + { + stretch=value; + InvokeOnElementStateChanged(); + } + } + + bool GuiImageFrameElement::GetEnabled() + { + return enabled; + } + + void GuiImageFrameElement::SetEnabled(bool value) + { + if(enabled!=value) + { + enabled=value; + InvokeOnElementStateChanged(); + } + } + +/*********************************************************************** +GuiPolygonElement +***********************************************************************/ + + GuiPolygonElement::GuiPolygonElement() + { + } + + Size GuiPolygonElement::GetSize() + { + return size; + } + + void GuiPolygonElement::SetSize(Size value) + { + if(size!=value) + { + size=value; + InvokeOnElementStateChanged(); + } + } + + const Point& GuiPolygonElement::GetPoint(vint index) + { + return points[index]; + } + + vint GuiPolygonElement::GetPointCount() + { + return points.Count(); + } + + void GuiPolygonElement::SetPoints(const Point* p, vint count) + { + points.Resize(count); + if(count>0) + { + memcpy(&points[0], p, sizeof(*p)*count); + } + InvokeOnElementStateChanged(); + } + + const GuiPolygonElement::PointArray& GuiPolygonElement::GetPointsArray() + { + return points; + } + + void GuiPolygonElement::SetPointsArray(const PointArray& value) + { + CopyFrom(points, value); + InvokeOnElementStateChanged(); + } + + Color GuiPolygonElement::GetBorderColor() + { + return borderColor; + } + + void GuiPolygonElement::SetBorderColor(Color value) + { + if(borderColor!=value) + { + borderColor=value; + InvokeOnElementStateChanged(); + } + } + + Color GuiPolygonElement::GetBackgroundColor() + { + return backgroundColor; + } + + void GuiPolygonElement::SetBackgroundColor(Color value) + { + if(backgroundColor!=value) + { + backgroundColor=value; + InvokeOnElementStateChanged(); + } + } + } + } +} + +/*********************************************************************** +.\GRAPHICSELEMENT\GUIGRAPHICSHOST.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace compositions + { + using namespace collections; + using namespace controls; + using namespace elements; + using namespace theme; + +/*********************************************************************** +GuiGraphicsTimerManager +***********************************************************************/ + + GuiGraphicsTimerManager::GuiGraphicsTimerManager() + { + } + + GuiGraphicsTimerManager::~GuiGraphicsTimerManager() + { + } + + void GuiGraphicsTimerManager::AddCallback(Ptr callback) + { + callbacks.Add(callback); + } + + void GuiGraphicsTimerManager::Play() + { + for (vint i = callbacks.Count() - 1; i >= 0; i--) + { + auto callback = callbacks[i]; + if (!callback->Play()) + { + callbacks.RemoveAt(i); + } + } + } + +/*********************************************************************** +IGuiAltAction +***********************************************************************/ + + const wchar_t* const IGuiAltAction::Identifier = L"vl::presentation::compositions::IGuiAltAction"; + const wchar_t* const IGuiAltActionContainer::Identifier = L"vl::presentation::compositions::IGuiAltAction"; + const wchar_t* const IGuiAltActionHost::Identifier = L"vl::presentation::compositions::IGuiAltAction"; + + bool IGuiAltAction::IsLegalAlt(const WString& alt) + { + for (vint i = 0; i < alt.Length(); i++) + { + if (alt[i] < L'A' || L'Z' < alt[i]) + { + return false; + } + } + return true; + } + + void IGuiAltActionHost::CollectAltActionsFromControl(controls::GuiControl* control, collections::Group& actions) + { + List controls; + controls.Add(control); + vint current = 0; + + while (current < controls.Count()) + { + GuiControl* control = controls[current++]; + + if (auto container = control->QueryTypedService()) + { + vint count = container->GetAltActionCount(); + for (vint i = 0; i < count; i++) + { + auto action = container->GetAltAction(i); + actions.Add(action->GetAlt(), action); + } + continue; + } + else if (auto action = control->QueryTypedService()) + { + if (action->IsAltAvailable()) + { + if (action->IsAltEnabled()) + { + actions.Add(action->GetAlt(), action); + continue; + } + } + } + + vint count = control->GetChildrenCount(); + for (vint i = 0; i < count; i++) + { + controls.Add(control->GetChild(i)); + } + } + } + +/*********************************************************************** +GuiGraphicsHost +***********************************************************************/ + + void GuiGraphicsHost::EnterAltHost(IGuiAltActionHost* host) + { + ClearAltHost(); + + Group actions; + host->CollectAltActions(actions); + if (actions.Count() == 0) + { + CloseAltHost(); + return; + } + + host->OnActivatedAltHost(currentAltHost); + currentAltHost = host; + CreateAltTitles(actions); + } + + void GuiGraphicsHost::LeaveAltHost() + { + if (currentAltHost) + { + ClearAltHost(); + auto previousHost = currentAltHost->GetPreviousAltHost(); + currentAltHost->OnDeactivatedAltHost(); + currentAltHost = previousHost; + + if (currentAltHost) + { + Group actions; + currentAltHost->CollectAltActions(actions); + CreateAltTitles(actions); + } + } + } + + bool GuiGraphicsHost::EnterAltKey(wchar_t key) + { + currentAltPrefix += key; + vint index = currentActiveAltActions.Keys().IndexOf(currentAltPrefix); + if (index == -1) + { + if (FilterTitles() == 0) + { + currentAltPrefix = currentAltPrefix.Left(currentAltPrefix.Length() - 1); + FilterTitles(); + } + } + else + { + auto action = currentActiveAltActions.Values()[index]; + if (action->GetActivatingAltHost()) + { + EnterAltHost(action->GetActivatingAltHost()); + } + else + { + CloseAltHost(); + } + action->OnActiveAlt(); + return true; + } + return false; + } + + void GuiGraphicsHost::LeaveAltKey() + { + if (currentAltPrefix.Length() >= 1) + { + currentAltPrefix = currentAltPrefix.Left(currentAltPrefix.Length() - 1); + } + FilterTitles(); + } + + void GuiGraphicsHost::CreateAltTitles(const collections::Group& actions) + { + if (currentAltHost) + { + vint count = actions.Count(); + for (vint i = 0; i < count; i++) + { + WString key = actions.Keys()[i]; + const auto& values = actions.GetByIndex(i); + vint numberLength = 0; + if (values.Count() == 1 && key.Length() > 0) + { + numberLength = 0; + } + else if (values.Count() <= 10) + { + numberLength = 1; + } + else if (values.Count() <= 100) + { + numberLength = 2; + } + else if (values.Count() <= 1000) + { + numberLength = 3; + } + else + { + continue; + } + + FOREACH_INDEXER(IGuiAltAction*, action, index, values) + { + WString key = actions.Keys()[i]; + if (numberLength > 0) + { + WString number = itow(index); + while (number.Length() < numberLength) + { + number = L"0" + number; + } + key += number; + } + currentActiveAltActions.Add(key, action); + } + } + + count = currentActiveAltActions.Count(); + auto window = dynamic_cast(currentAltHost->GetAltComposition()->GetRelatedControlHost()); + for (vint i = 0; i < count; i++) + { + auto key = currentActiveAltActions.Keys()[i]; + auto composition = currentActiveAltActions.Values()[i]->GetAltComposition(); + + auto label = new GuiLabel(theme::ThemeName::ShortcutKey); + if (auto labelStyle = window->GetControlTemplateObject(true)->GetShortcutKeyTemplate()) + { + label->SetControlTemplate(labelStyle); + } + label->SetText(key); + composition->AddChild(label->GetBoundsComposition()); + currentActiveAltTitles.Add(key, label); + } + + FilterTitles(); + } + } + + vint GuiGraphicsHost::FilterTitles() + { + vint count = currentActiveAltTitles.Count(); + vint visibles = 0; + for (vint i = 0; i < count; i++) + { + auto key = currentActiveAltTitles.Keys()[i]; + auto value = currentActiveAltTitles.Values()[i]; + if (key.Length() >= currentAltPrefix.Length() && key.Left(currentAltPrefix.Length()) == currentAltPrefix) + { + value->SetVisible(true); + if (currentAltPrefix.Length() <= key.Length()) + { + value->SetText( + key + .Insert(currentAltPrefix.Length(), L"[") + .Insert(currentAltPrefix.Length() + 2, L"]") + ); + } + else + { + value->SetText(key); + } + visibles++; + } + else + { + value->SetVisible(false); + } + } + return visibles; + } + + void GuiGraphicsHost::ClearAltHost() + { + FOREACH(GuiControl*, title, currentActiveAltTitles.Values()) + { + SafeDeleteControl(title); + } + currentActiveAltActions.Clear(); + currentActiveAltTitles.Clear(); + currentAltPrefix = L""; + } + + void GuiGraphicsHost::CloseAltHost() + { + ClearAltHost(); + while (currentAltHost) + { + currentAltHost->OnDeactivatedAltHost(); + currentAltHost = currentAltHost->GetPreviousAltHost(); + } + } + + void GuiGraphicsHost::RefreshRelatedHostRecord(INativeWindow* nativeWindow) + { + hostRecord.nativeWindow = nativeWindow; + hostRecord.renderTarget = nativeWindow ? GetGuiGraphicsResourceManager()->GetRenderTarget(nativeWindow) : nullptr; + windowComposition->UpdateRelatedHostRecord(&hostRecord); + } + + void GuiGraphicsHost::DisconnectCompositionInternal(GuiGraphicsComposition* composition) + { + for(vint i=0;iChildren().Count();i++) + { + DisconnectCompositionInternal(composition->Children().Get(i)); + } + if(mouseCaptureComposition==composition) + { + if(hostRecord.nativeWindow) + { + hostRecord.nativeWindow->ReleaseCapture(); + } + mouseCaptureComposition=0; + } + if(focusedComposition==composition) + { + focusedComposition=0; + } + mouseEnterCompositions.Remove(composition); + } + + void GuiGraphicsHost::MouseCapture(const NativeWindowMouseInfo& info) + { + if(hostRecord.nativeWindow && (info.left || info.middle || info.right)) + { + if(!hostRecord.nativeWindow->IsCapturing() && !info.nonClient) + { + hostRecord.nativeWindow->RequireCapture(); + mouseCaptureComposition=windowComposition->FindComposition(Point(info.x, info.y), true); + } + } + } + + void GuiGraphicsHost::MouseUncapture(const NativeWindowMouseInfo& info) + { + if(hostRecord.nativeWindow && !(info.left || info.middle || info.right)) + { + hostRecord.nativeWindow->ReleaseCapture(); + mouseCaptureComposition=0; + } + } + + void GuiGraphicsHost::OnCharInput(const NativeWindowCharInfo& info, GuiGraphicsComposition* composition, GuiCharEvent GuiGraphicsEventReceiver::* eventReceiverEvent) + { + List compositions; + while(composition) + { + if(composition->HasEventReceiver()) + { + compositions.Add(composition); + } + composition=composition->GetParent(); + } + + GuiCharEventArgs arguments(composition); + (NativeWindowCharInfo&)arguments=info; + + for(vint i=compositions.Count()-1;i>=0;i--) + { + compositions[i]->GetEventReceiver()->previewCharInput.Execute(arguments); + if(arguments.handled) + { + return; + } + } + + for(vint i=0;iGetEventReceiver()->*eventReceiverEvent).Execute(arguments); + if(arguments.handled) + { + return; + } + } + } + + void GuiGraphicsHost::OnKeyInput(const NativeWindowKeyInfo& info, GuiGraphicsComposition* composition, GuiKeyEvent GuiGraphicsEventReceiver::* eventReceiverEvent) + { + List compositions; + while(composition) + { + if(composition->HasEventReceiver()) + { + compositions.Add(composition); + } + composition=composition->GetParent(); + } + + GuiKeyEventArgs arguments(composition); + (NativeWindowKeyInfo&)arguments=info; + + for(vint i=compositions.Count()-1;i>=0;i--) + { + compositions[i]->GetEventReceiver()->previewKey.Execute(arguments); + if(arguments.handled) + { + return; + } + } + + for(vint i=0;iGetEventReceiver()->*eventReceiverEvent).Execute(arguments); + if(arguments.handled) + { + return; + } + } + } + + void GuiGraphicsHost::RaiseMouseEvent(GuiMouseEventArgs& arguments, GuiGraphicsComposition* composition, GuiMouseEvent GuiGraphicsEventReceiver::* eventReceiverEvent) + { + arguments.compositionSource=composition; + arguments.eventSource=0; + vint x=arguments.x; + vint y=arguments.y; + + while(composition) + { + if(composition->HasEventReceiver()) + { + if(!arguments.eventSource) + { + arguments.eventSource=composition; + } + GuiGraphicsEventReceiver* eventReceiver=composition->GetEventReceiver(); + (eventReceiver->*eventReceiverEvent).Execute(arguments); + if(arguments.handled) + { + break; + } + } + + GuiGraphicsComposition* parent=composition->GetParent(); + if(parent) + { + Rect parentBounds=parent->GetBounds(); + Rect clientArea=parent->GetClientArea(); + Rect childBounds=composition->GetBounds(); + + x+=childBounds.x1+(clientArea.x1-parentBounds.x1); + y+=childBounds.y1+(clientArea.y1-parentBounds.y1); + arguments.x=x; + arguments.y=y; + } + composition=parent; + } + } + + void GuiGraphicsHost::OnMouseInput(const NativeWindowMouseInfo& info, GuiMouseEvent GuiGraphicsEventReceiver::* eventReceiverEvent) + { + GuiGraphicsComposition* composition=0; + if(mouseCaptureComposition) + { + composition=mouseCaptureComposition; + } + else + { + composition=windowComposition->FindComposition(Point(info.x, info.y), true); + } + if(composition) + { + Rect bounds=composition->GetGlobalBounds(); + GuiMouseEventArgs arguments; + (NativeWindowMouseInfo&)arguments=info; + arguments.x-=bounds.x1; + arguments.y-=bounds.y1; + RaiseMouseEvent(arguments, composition, eventReceiverEvent); + } + } + + INativeWindowListener::HitTestResult GuiGraphicsHost::HitTest(Point location) + { + Rect bounds = hostRecord.nativeWindow->GetBounds(); + Rect clientBounds = hostRecord.nativeWindow->GetClientBoundsInScreen(); + Point clientLocation(location.x + bounds.x1 - clientBounds.x1, location.y + bounds.y1 - clientBounds.y1); + GuiGraphicsComposition* hitComposition = windowComposition->FindComposition(clientLocation, false); + while (hitComposition) + { + INativeWindowListener::HitTestResult result = hitComposition->GetAssociatedHitTestResult(); + if (result == INativeWindowListener::NoDecision) + { + hitComposition = hitComposition->GetParent(); + } + else + { + return result; + } + } + return INativeWindowListener::NoDecision; + } + + void GuiGraphicsHost::Moving(Rect& bounds, bool fixSizeOnly) + { + Rect oldBounds = hostRecord.nativeWindow->GetBounds(); + minSize = windowComposition->GetPreferredBounds().GetSize(); + Size minWindowSize = minSize + (oldBounds.GetSize() - hostRecord.nativeWindow->GetClientSize()); + if (bounds.Width() < minWindowSize.x) + { + if (fixSizeOnly) + { + if (bounds.Width() < minWindowSize.x) + { + bounds.x2 = bounds.x1 + minWindowSize.x; + } + } + else if (oldBounds.x1 != bounds.x1) + { + bounds.x1 = oldBounds.x2 - minWindowSize.x; + } + else if (oldBounds.x2 != bounds.x2) + { + bounds.x2 = oldBounds.x1 + minWindowSize.x; + } + } + if (bounds.Height() < minWindowSize.y) + { + if (fixSizeOnly) + { + if (bounds.Height() < minWindowSize.y) + { + bounds.y2 = bounds.y1 + minWindowSize.y; + } + } + else if (oldBounds.y1 != bounds.y1) + { + bounds.y1 = oldBounds.y2 - minWindowSize.y; + } + else if (oldBounds.y2 != bounds.y2) + { + bounds.y2 = oldBounds.y1 + minWindowSize.y; + } + } + } + + void GuiGraphicsHost::Moved() + { + Size size = hostRecord.nativeWindow->GetClientSize(); + if (previousClientSize != size) + { + previousClientSize = size; + minSize = windowComposition->GetPreferredBounds().GetSize(); + needRender = true; + } + } + + void GuiGraphicsHost::Paint() + { + if (!supressPaint) + { + needRender = true; + } + } + + void GuiGraphicsHost::LeftButtonDown(const NativeWindowMouseInfo& info) + { + CloseAltHost(); + MouseCapture(info); + OnMouseInput(info, &GuiGraphicsEventReceiver::leftButtonDown); + } + + void GuiGraphicsHost::LeftButtonUp(const NativeWindowMouseInfo& info) + { + OnMouseInput(info, &GuiGraphicsEventReceiver::leftButtonUp); + MouseUncapture(info); + } + + void GuiGraphicsHost::LeftButtonDoubleClick(const NativeWindowMouseInfo& info) + { + LeftButtonDown(info); + OnMouseInput(info, &GuiGraphicsEventReceiver::leftButtonDoubleClick); + } + + void GuiGraphicsHost::RightButtonDown(const NativeWindowMouseInfo& info) + { + CloseAltHost(); + MouseCapture(info); + OnMouseInput(info, &GuiGraphicsEventReceiver::rightButtonDown); + } + + void GuiGraphicsHost::RightButtonUp(const NativeWindowMouseInfo& info) + { + OnMouseInput(info, &GuiGraphicsEventReceiver::rightButtonUp); + MouseUncapture(info); + } + + void GuiGraphicsHost::RightButtonDoubleClick(const NativeWindowMouseInfo& info) + { + RightButtonDown(info); + OnMouseInput(info, &GuiGraphicsEventReceiver::rightButtonDoubleClick); + } + + void GuiGraphicsHost::MiddleButtonDown(const NativeWindowMouseInfo& info) + { + CloseAltHost(); + MouseCapture(info); + OnMouseInput(info, &GuiGraphicsEventReceiver::middleButtonDown); + } + + void GuiGraphicsHost::MiddleButtonUp(const NativeWindowMouseInfo& info) + { + OnMouseInput(info, &GuiGraphicsEventReceiver::middleButtonUp); + MouseUncapture(info); + } + + void GuiGraphicsHost::MiddleButtonDoubleClick(const NativeWindowMouseInfo& info) + { + MiddleButtonDown(info); + OnMouseInput(info, &GuiGraphicsEventReceiver::middleButtonDoubleClick); + } + + void GuiGraphicsHost::HorizontalWheel(const NativeWindowMouseInfo& info) + { + OnMouseInput(info, &GuiGraphicsEventReceiver::horizontalWheel); + } + + void GuiGraphicsHost::VerticalWheel(const NativeWindowMouseInfo& info) + { + OnMouseInput(info, &GuiGraphicsEventReceiver::verticalWheel); + } + + void GuiGraphicsHost::MouseMoving(const NativeWindowMouseInfo& info) + { + CompositionList newCompositions; + { + GuiGraphicsComposition* composition = windowComposition->FindComposition(Point(info.x, info.y), true); + while (composition) + { + newCompositions.Insert(0, composition); + composition = composition->GetParent(); + } + } + + vint firstDifferentIndex = mouseEnterCompositions.Count(); + for (vint i = 0; i < mouseEnterCompositions.Count(); i++) + { + if (i == newCompositions.Count()) + { + firstDifferentIndex = newCompositions.Count(); + break; + } + if (mouseEnterCompositions[i] != newCompositions[i]) + { + firstDifferentIndex = i; + break; + } + } + + for (vint i = mouseEnterCompositions.Count() - 1; i >= firstDifferentIndex; i--) + { + GuiGraphicsComposition* composition = mouseEnterCompositions[i]; + if (composition->HasEventReceiver()) + { + composition->GetEventReceiver()->mouseLeave.Execute(GuiEventArgs(composition)); + } + } + + CopyFrom(mouseEnterCompositions, newCompositions); + for (vint i = firstDifferentIndex; i < mouseEnterCompositions.Count(); i++) + { + GuiGraphicsComposition* composition = mouseEnterCompositions[i]; + if (composition->HasEventReceiver()) + { + composition->GetEventReceiver()->mouseEnter.Execute(GuiEventArgs(composition)); + } + } + + INativeCursor* cursor = 0; + if (newCompositions.Count() > 0) + { + cursor = newCompositions[newCompositions.Count() - 1]->GetRelatedCursor(); + } + if (cursor) + { + hostRecord.nativeWindow->SetWindowCursor(cursor); + } + else + { + hostRecord.nativeWindow->SetWindowCursor(GetCurrentController()->ResourceService()->GetDefaultSystemCursor()); + } + + OnMouseInput(info, &GuiGraphicsEventReceiver::mouseMove); + } + + void GuiGraphicsHost::MouseEntered() + { + } + + void GuiGraphicsHost::MouseLeaved() + { + for(vint i=mouseEnterCompositions.Count()-1;i>=0;i--) + { + GuiGraphicsComposition* composition=mouseEnterCompositions[i]; + if(composition->HasEventReceiver()) + { + composition->GetEventReceiver()->mouseLeave.Execute(GuiEventArgs(composition)); + } + } + mouseEnterCompositions.Clear(); + } + + void GuiGraphicsHost::KeyDown(const NativeWindowKeyInfo& info) + { + if (!info.ctrl && !info.shift && currentAltHost) + { + if (info.code == VKEY_ESCAPE) + { + LeaveAltHost(); + return; + } + else if (info.code == VKEY_BACK) + { + LeaveAltKey(); + } + else if (VKEY_NUMPAD0 <= info.code && info.code <= VKEY_NUMPAD9) + { + if (EnterAltKey((wchar_t)(L'0' + (info.code - VKEY_NUMPAD0)))) + { + supressAltKey = info.code; + return; + } + } + else if (('0' <= info.code && info.code <= '9') || ('A' <= info.code && info.code <= 'Z')) + { + if (EnterAltKey((wchar_t)info.code)) + { + supressAltKey = info.code; + return; + } + } + } + + if (currentAltHost) + { + return; + } + + if(shortcutKeyManager && shortcutKeyManager->Execute(info)) + { + return; + } + if(focusedComposition && focusedComposition->HasEventReceiver()) + { + OnKeyInput(info, focusedComposition, &GuiGraphicsEventReceiver::keyDown); + } + } + + void GuiGraphicsHost::KeyUp(const NativeWindowKeyInfo& info) + { + if (!info.ctrl && !info.shift && info.code == supressAltKey) + { + supressAltKey = 0; + return; + } + + if(focusedComposition && focusedComposition->HasEventReceiver()) + { + OnKeyInput(info, focusedComposition, &GuiGraphicsEventReceiver::keyUp); + } + } + + void GuiGraphicsHost::SysKeyDown(const NativeWindowKeyInfo& info) + { + if (!info.ctrl && !info.shift && info.code == VKEY_MENU && !currentAltHost) + { + if (auto window = dynamic_cast(windowComposition->Children()[0]->GetRelatedControlHost())) + { + if (auto altHost = window->QueryTypedService()) + { + if (!altHost->GetPreviousAltHost()) + { + EnterAltHost(altHost); + } + } + } + } + + if (currentAltHost) + { + return; + } + + if(focusedComposition && focusedComposition->HasEventReceiver()) + { + OnKeyInput(info, focusedComposition, &GuiGraphicsEventReceiver::systemKeyDown); + } + } + + void GuiGraphicsHost::SysKeyUp(const NativeWindowKeyInfo& info) + { + if (!info.ctrl && !info.shift && info.code == VKEY_MENU && hostRecord.nativeWindow) + { + if (hostRecord.nativeWindow) + { + hostRecord.nativeWindow->SupressAlt(); + } + } + + if (focusedComposition && focusedComposition->HasEventReceiver()) + { + OnKeyInput(info, focusedComposition, &GuiGraphicsEventReceiver::systemKeyUp); + } + } + + void GuiGraphicsHost::Char(const NativeWindowCharInfo& info) + { + if (!currentAltHost && !supressAltKey) + { + if(focusedComposition && focusedComposition->HasEventReceiver()) + { + OnCharInput(info, focusedComposition, &GuiGraphicsEventReceiver::charInput); + } + } + } + + void GuiGraphicsHost::GlobalTimer() + { + timerManager.Play(); + + DateTime now=DateTime::UtcTime(); + if(now.totalMilliseconds-lastCaretTime>=CaretInterval) + { + lastCaretTime=now.totalMilliseconds; + if(focusedComposition && focusedComposition->HasEventReceiver()) + { + focusedComposition->GetEventReceiver()->caretNotify.Execute(GuiEventArgs(focusedComposition)); + } + } + + Render(false); + } + + GuiGraphicsHost::GuiGraphicsHost(controls::GuiControlHost* _controlHost, GuiGraphicsComposition* boundsComposition) + :controlHost(_controlHost) + { + hostRecord.host = this; + windowComposition=new GuiWindowComposition; + windowComposition->SetMinSizeLimitation(GuiGraphicsComposition::LimitToElementAndChildren); + windowComposition->AddChild(boundsComposition); + RefreshRelatedHostRecord(nullptr); + } + + GuiGraphicsHost::~GuiGraphicsHost() + { + windowComposition->RemoveChild(windowComposition->Children()[0]); + NotifyFinalizeInstance(windowComposition); + if(shortcutKeyManager) + { + delete shortcutKeyManager; + shortcutKeyManager=0; + } + delete windowComposition; + } + + INativeWindow* GuiGraphicsHost::GetNativeWindow() + { + return hostRecord.nativeWindow; + } + + void GuiGraphicsHost::SetNativeWindow(INativeWindow* _nativeWindow) + { + if (hostRecord.nativeWindow != _nativeWindow) + { + if (hostRecord.nativeWindow) + { + GetCurrentController()->CallbackService()->UninstallListener(this); + hostRecord.nativeWindow->UninstallListener(this); + } + + if (_nativeWindow) + { + _nativeWindow->InstallListener(this); + GetCurrentController()->CallbackService()->InstallListener(this); + previousClientSize = _nativeWindow->GetClientSize(); + minSize = windowComposition->GetPreferredBounds().GetSize(); + _nativeWindow->SetCaretPoint(caretPoint); + needRender = true; + } + + RefreshRelatedHostRecord(_nativeWindow); + } + } + + GuiGraphicsComposition* GuiGraphicsHost::GetMainComposition() + { + return windowComposition; + } + + void GuiGraphicsHost::Render(bool forceUpdate) + { + if (!forceUpdate && !needRender) + { + return; + } + needRender = false; + + if(hostRecord.nativeWindow && hostRecord.nativeWindow->IsVisible()) + { + supressPaint = true; + hostRecord.renderTarget->StartRendering(); + windowComposition->Render(Size()); + { + auto bounds = windowComposition->GetBounds(); + auto preferred = windowComposition->GetPreferredBounds(); + auto width = bounds.Width() > preferred.Width() ? bounds.Width() : preferred.Width(); + auto height = bounds.Height() > preferred.Height() ? bounds.Height() : preferred.Height(); + if (width != bounds.Width() || height != bounds.Height()) + { + controlHost->UpdateClientSizeAfterRendering(Size(width, height)); + } + } + auto result = hostRecord.renderTarget->StopRendering(); + hostRecord.nativeWindow->RedrawContent(); + supressPaint = false; + + switch (result) + { + case RenderTargetFailure::ResizeWhileRendering: + { + GetGuiGraphicsResourceManager()->ResizeRenderTarget(hostRecord.nativeWindow); + needRender = true; + } + break; + case RenderTargetFailure::LostDevice: + { + windowComposition->UpdateRelatedHostRecord(nullptr); + GetGuiGraphicsResourceManager()->RecreateRenderTarget(hostRecord.nativeWindow); + RefreshRelatedHostRecord(hostRecord.nativeWindow); + needRender = true; + } + break; + default:; + } + } + } + + void GuiGraphicsHost::RequestRender() + { + needRender = true; + } + + IGuiShortcutKeyManager* GuiGraphicsHost::GetShortcutKeyManager() + { + return shortcutKeyManager; + } + + void GuiGraphicsHost::SetShortcutKeyManager(IGuiShortcutKeyManager* value) + { + shortcutKeyManager=value; + } + + bool GuiGraphicsHost::SetFocus(GuiGraphicsComposition* composition) + { + if(!composition || composition->GetRelatedGraphicsHost()!=this) + { + return false; + } + if(focusedComposition && focusedComposition->HasEventReceiver()) + { + GuiEventArgs arguments; + arguments.compositionSource=focusedComposition; + arguments.eventSource=focusedComposition; + focusedComposition->GetEventReceiver()->lostFocus.Execute(arguments); + } + focusedComposition=composition; + SetCaretPoint(Point(0, 0)); + if(focusedComposition && focusedComposition->HasEventReceiver()) + { + GuiEventArgs arguments; + arguments.compositionSource=focusedComposition; + arguments.eventSource=focusedComposition; + focusedComposition->GetEventReceiver()->gotFocus.Execute(arguments); + } + return true; + } + + GuiGraphicsComposition* GuiGraphicsHost::GetFocusedComposition() + { + return focusedComposition; + } + + Point GuiGraphicsHost::GetCaretPoint() + { + return caretPoint; + } + + void GuiGraphicsHost::SetCaretPoint(Point value, GuiGraphicsComposition* referenceComposition) + { + if (referenceComposition) + { + Rect bounds = referenceComposition->GetGlobalBounds(); + value.x += bounds.x1; + value.y += bounds.y1; + } + caretPoint = value; + if (hostRecord.nativeWindow) + { + hostRecord.nativeWindow->SetCaretPoint(caretPoint); + } + } + + GuiGraphicsTimerManager* GuiGraphicsHost::GetTimerManager() + { + return &timerManager; + } + + void GuiGraphicsHost::DisconnectComposition(GuiGraphicsComposition* composition) + { + DisconnectCompositionInternal(composition); + } + +/*********************************************************************** +GuiShortcutKeyItem +***********************************************************************/ + + GuiShortcutKeyItem::GuiShortcutKeyItem(GuiShortcutKeyManager* _shortcutKeyManager, bool _ctrl, bool _shift, bool _alt, vint _key) + :shortcutKeyManager(_shortcutKeyManager) + ,ctrl(_ctrl) + ,shift(_shift) + ,alt(_alt) + ,key(_key) + { + } + + GuiShortcutKeyItem::~GuiShortcutKeyItem() + { + } + + IGuiShortcutKeyManager* GuiShortcutKeyItem::GetManager() + { + return shortcutKeyManager; + } + + WString GuiShortcutKeyItem::GetName() + { + WString name; + if(ctrl) name+=L"Ctrl+"; + if(shift) name+=L"Shift+"; + if(alt) name+=L"Alt+"; + name+=GetCurrentController()->InputService()->GetKeyName(key); + return name; + } + + bool GuiShortcutKeyItem::CanActivate(const NativeWindowKeyInfo& info) + { + return + info.ctrl==ctrl && + info.shift==shift && + info.alt==alt && + info.code==key; + } + + bool GuiShortcutKeyItem::CanActivate(bool _ctrl, bool _shift, bool _alt, vint _key) + { + return + _ctrl==ctrl && + _shift==shift && + _alt==alt && + _key==key; + } + +/*********************************************************************** +GuiShortcutKeyManager +***********************************************************************/ + + GuiShortcutKeyManager::GuiShortcutKeyManager() + { + } + + GuiShortcutKeyManager::~GuiShortcutKeyManager() + { + } + + vint GuiShortcutKeyManager::GetItemCount() + { + return shortcutKeyItems.Count(); + } + + IGuiShortcutKeyItem* GuiShortcutKeyManager::GetItem(vint index) + { + return shortcutKeyItems[index].Obj(); + } + + bool GuiShortcutKeyManager::Execute(const NativeWindowKeyInfo& info) + { + bool executed=false; + FOREACH(Ptr, item, shortcutKeyItems) + { + if(item->CanActivate(info)) + { + GuiEventArgs arguments; + item->Executed.Execute(arguments); + executed=true; + } + } + return executed; + } + + IGuiShortcutKeyItem* GuiShortcutKeyManager::CreateShortcut(bool ctrl, bool shift, bool alt, vint key) + { + FOREACH(Ptr, item, shortcutKeyItems) + { + if(item->CanActivate(ctrl, shift, alt, key)) + { + return item.Obj(); + } + } + Ptr item=new GuiShortcutKeyItem(this, ctrl, shift, alt, key); + shortcutKeyItems.Add(item); + return item.Obj(); + } + + bool GuiShortcutKeyManager::DestroyShortcut(bool ctrl, bool shift, bool alt, vint key) + { + FOREACH(Ptr, item, shortcutKeyItems) + { + if(item->CanActivate(ctrl, shift, alt, key)) + { + shortcutKeyItems.Remove(item.Obj()); + return true; + } + } + return false; + } + + IGuiShortcutKeyItem* GuiShortcutKeyManager::TryGetShortcut(bool ctrl, bool shift, bool alt, vint key) + { + FOREACH(Ptr, item, shortcutKeyItems) + { + if(item->CanActivate(ctrl, shift, alt, key)) + { + return item.Obj(); + } + } + return 0; + } + } + } +} + +/*********************************************************************** +.\GRAPHICSELEMENT\GUIGRAPHICSRESOURCEMANAGER.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + namespace elements + { + using namespace collections; + +/*********************************************************************** +GuiGraphicsResourceManager +***********************************************************************/ + + GuiGraphicsResourceManager::GuiGraphicsResourceManager() + { + } + + GuiGraphicsResourceManager::~GuiGraphicsResourceManager() + { + } + + bool GuiGraphicsResourceManager::RegisterElementFactory(IGuiGraphicsElementFactory* factory) + { + if(elementFactories.Keys().Contains(factory->GetElementTypeName())) + { + return false; + } + else + { + elementFactories.Add(factory->GetElementTypeName(), factory); + return true; + } + } + + bool GuiGraphicsResourceManager::RegisterRendererFactory(const WString& elementTypeName, IGuiGraphicsRendererFactory* factory) + { + if(rendererFactories.Keys().Contains(elementTypeName)) + { + return false; + } + else + { + rendererFactories.Add(elementTypeName, factory); + return true; + } + } + + IGuiGraphicsElementFactory* GuiGraphicsResourceManager::GetElementFactory(const WString& elementTypeName) + { + vint index=elementFactories.Keys().IndexOf(elementTypeName); + return index==-1?0:elementFactories.Values().Get(index).Obj(); + } + + IGuiGraphicsRendererFactory* GuiGraphicsResourceManager::GetRendererFactory(const WString& elementTypeName) + { + vint index=rendererFactories.Keys().IndexOf(elementTypeName); + return index==-1?nullptr:rendererFactories.Values().Get(index).Obj(); + } + + GuiGraphicsResourceManager* guiGraphicsResourceManager=0; + + GuiGraphicsResourceManager* GetGuiGraphicsResourceManager() + { + return guiGraphicsResourceManager; + } + + void SetGuiGraphicsResourceManager(GuiGraphicsResourceManager* resourceManager) + { + guiGraphicsResourceManager=resourceManager; + } + + bool RegisterFactories(IGuiGraphicsElementFactory* elementFactory, IGuiGraphicsRendererFactory* rendererFactory) + { + if(guiGraphicsResourceManager && elementFactory && rendererFactory) + { + if(guiGraphicsResourceManager->RegisterElementFactory(elementFactory)) + { + if(guiGraphicsResourceManager->RegisterRendererFactory(elementFactory->GetElementTypeName(), rendererFactory)) + { + return true; + } + } + } + return false; + } + } + } +} + +/*********************************************************************** +.\GRAPHICSELEMENT\GUIGRAPHICSTEXTELEMENT.CPP +***********************************************************************/ + +namespace vl +{ + using namespace collections; + + namespace presentation + { + namespace elements + { + namespace text + { + +/*********************************************************************** +text::TextLine +***********************************************************************/ + + TextLine::TextLine() + :text(0) + ,att(0) + ,availableOffsetCount(0) + ,bufferLength(0) + ,dataLength(0) + ,lexerFinalState(-1) + ,contextFinalState(-1) + { + } + + TextLine::~TextLine() + { + } + + vint TextLine::CalculateBufferLength(vint dataLength) + { + if(dataLength<1)dataLength=1; + vint bufferLength=dataLength-dataLength%BlockSize; + if(bufferLengthdataLength || inputCount<0) return false; + + vint newDataLength=dataLength-count+inputCount; + vint newBufferLength=CalculateBufferLength(newDataLength); + if(newBufferLength!=bufferLength) + { + wchar_t* newText=new wchar_t[newBufferLength]; + memcpy(newText, text, start*sizeof(wchar_t)); + memcpy(newText+start, input, inputCount*sizeof(wchar_t)); + memcpy(newText+start+inputCount, text+start+count, (dataLength-start-count)*sizeof(wchar_t)); + + CharAtt* newAtt=new CharAtt[newBufferLength]; + memcpy(newAtt, att, start*sizeof(CharAtt)); + memset(newAtt+start, 0, inputCount*sizeof(CharAtt)); + memcpy(newAtt+start+inputCount, att+start+count, (dataLength-start-count)*sizeof(CharAtt)); + + delete[] text; + delete[] att; + text=newText; + att=newAtt; + } + else + { + memmove(text+start+inputCount, text+start+count, (dataLength-start-count)*sizeof(wchar_t)); + memmove(att+start+inputCount, att+start+count, (dataLength-start-count)*sizeof(CharAtt)); + memcpy(text+start, input, inputCount*sizeof(wchar_t)); + memset(att+start, 0, inputCount*sizeof(CharAtt)); + } + dataLength=newDataLength; + bufferLength=newBufferLength; + if(availableOffsetCount>start) + { + availableOffsetCount=start; + } + + return true; + } + + TextLine TextLine::Split(vint index) + { + if(index<0 || index>dataLength) return TextLine(); + vint count=dataLength-index; + TextLine line; + line.Initialize(); + line.Modify(0, 0, text+index, count); + memcpy(line.att, att+index, count*sizeof(CharAtt)); + Modify(index, count, L"", 0); + return line; + } + + void TextLine::AppendAndFinalize(TextLine& line) + { + vint oldDataLength=dataLength; + Modify(oldDataLength, 0, line.text, line.dataLength); + memcpy(att+oldDataLength, line.att, line.dataLength*sizeof(CharAtt)); + line.Finalize(); + } + +/*********************************************************************** +text::CharMeasurer +***********************************************************************/ + + CharMeasurer::CharMeasurer(vint _rowHeight) + :rowHeight(_rowHeight) + { + memset(widths, 0, sizeof(widths)); + } + + CharMeasurer::~CharMeasurer() + { + } + + void CharMeasurer::SetRenderTarget(IGuiGraphicsRenderTarget* value) + { + if(oldRenderTarget!=value) + { + oldRenderTarget=value; + rowHeight=GetRowHeightInternal(oldRenderTarget); + memset(widths, 0, sizeof(widths)); + } + } + + vint CharMeasurer::MeasureWidth(UnicodeCodePoint codePoint) + { + vuint32_t index = codePoint.GetCodePoint(); + if (0 <= index && index < 65536) + { + vint w = widths[index]; + if (w == 0) + { + widths[index] = w = MeasureWidthInternal(codePoint, oldRenderTarget); + } + return w; + } + else if (index < 0x110000) + { + return MeasureWidthInternal(codePoint, oldRenderTarget); + } + else + { + return 0; + } + } + + vint CharMeasurer::GetRowHeight() + { + return rowHeight; + } + +/*********************************************************************** +text::TextLines +***********************************************************************/ + + TextLines::TextLines(GuiColorizedTextElement* _ownerElement) + :ownerElement(_ownerElement) + ,charMeasurer(0) + ,renderTarget(0) + ,tabWidth(1) + ,tabSpaceCount(4) + ,passwordChar(L'\0') + { + TextLine line; + line.Initialize(); + lines.Add(line); + } + + TextLines::~TextLines() + { + RemoveLines(0, lines.Count()); + } + + //-------------------------------------------------------- + + vint TextLines::GetCount() + { + return lines.Count(); + } + + TextLine& TextLines::GetLine(vint row) + { + return lines[row]; + } + + CharMeasurer* TextLines::GetCharMeasurer() + { + return charMeasurer; + } + + void TextLines::SetCharMeasurer(CharMeasurer* value) + { + charMeasurer=value; + if(charMeasurer) charMeasurer->SetRenderTarget(renderTarget); + ClearMeasurement(); + } + + IGuiGraphicsRenderTarget* TextLines::GetRenderTarget() + { + return renderTarget; + } + + void TextLines::SetRenderTarget(IGuiGraphicsRenderTarget* value) + { + renderTarget=value; + if(charMeasurer) charMeasurer->SetRenderTarget(renderTarget); + ClearMeasurement(); + } + + WString TextLines::GetText(TextPos start, TextPos end) + { + if(!IsAvailable(start) || !IsAvailable(end) || start>end) return L""; + + if(start.row==end.row) + { + return WString(lines[start.row].text+start.column, end.column-start.column); + } + + vint count=0; + for(vint i=start.row+1;i buffer; + buffer.Resize(count+(end.row-start.row)*2); + wchar_t* writing=&buffer[0]; + + for(vint i=start.row;i<=end.row;i++) + { + wchar_t* text=lines[i].text; + vint chars=0; + if(i==start.row) + { + text+=start.column; + chars=lines[i].dataLength-start.column; + } + else if(i==end.row) + { + chars=end.column; + } + else + { + chars=lines[i].dataLength; + } + + if(i!=start.row) + { + *writing++=L'\r'; + *writing++=L'\n'; + } + memcpy(writing, text, chars*sizeof(wchar_t)); + writing+=chars; + } + return WString(&buffer[0], buffer.Count()); + } + + WString TextLines::GetText() + { + return GetText(TextPos(0, 0), TextPos(lines.Count()-1, lines[lines.Count()-1].dataLength)); + } + + void TextLines::SetText(const WString& value) + { + Modify(TextPos(0, 0), TextPos(lines.Count()-1, lines[lines.Count()-1].dataLength), value); + } + + //-------------------------------------------------------- + + bool TextLines::RemoveLines(vint start, vint count) + { + if(start<0 || count<0 || start+count>lines.Count()) return false; + for(vint i=start+count-1;i>=start;i--) + { + lines[i].Finalize(); + } + lines.RemoveRange(start, count); + return true; + } + + bool TextLines::IsAvailable(TextPos pos) + { + return 0<=pos.row && pos.row=lines.Count()) + { + return TextPos(lines.Count()-1, lines[lines.Count()-1].dataLength); + } + else + { + TextLine& line=lines[pos.row]; + if(pos.column<0) + { + return TextPos(pos.row, 0); + } + else if(pos.column>line.dataLength) + { + return TextPos(pos.row, line.dataLength); + } + else + { + return pos; + } + } + } + + TextPos TextLines::Modify(TextPos start, TextPos end, const wchar_t** inputs, vint* inputCounts, vint rows) + { + if(!IsAvailable(start) || !IsAvailable(end) || start>end) return TextPos(-1, -1); + if (ownerElement) + { + ownerElement->InvokeOnElementStateChanged(); + } + + if(rows==1) + { + if(start.row==end.row) + { + lines[start.row].Modify(start.column, end.column-start.column, inputs[0], inputCounts[0]); + } + else + { + if(end.row-start.row>1) + { + RemoveLines(start.row+1, end.row-start.row-1); + } + vint modifyCount=lines[start.row].dataLength-start.column+end.column; + lines[start.row].AppendAndFinalize(lines[start.row+1]); + lines.RemoveAt(start.row+1); + lines[start.row].Modify(start.column, modifyCount, inputs[0], inputCounts[0]); + } + return TextPos(start.row, start.column+inputCounts[0]); + } + + if(start.row==end.row) + { + TextLine newLine=lines[start.row].Split(end.column); + lines.Insert(start.row+1, newLine); + end=TextPos(start.row+1, 0); + } + + vint oldMiddleLines=end.row-start.row-1; + vint newMiddleLines=rows-2; + if(oldMiddleLinesnewMiddleLines) + { + RemoveLines(start.row+newMiddleLines+1, oldMiddleLines-newMiddleLines); + } + end.row+=newMiddleLines-oldMiddleLines; + + lines[start.row].Modify(start.column, lines[start.row].dataLength-start.column, inputs[0], inputCounts[0]); + lines[end.row].Modify(0, end.column, inputs[rows-1], inputCounts[rows-1]); + for(vint i=1;i inputs; + List inputCounts; + const wchar_t* previous=input; + const wchar_t* current=input; + + while(true) + { + if(current==input+inputCount) + { + inputs.Add(previous); + inputCounts.Add(current-previous); + break; + } + else if(*current==L'\r' || *current==L'\n') + { + inputs.Add(previous); + inputCounts.Add(current-previous); + previous=current+(current[1]==L'\n'?2:1); + current=previous; + } + else + { + current++; + } + } + + return Modify(start, end, &inputs[0], &inputCounts[0], inputs.Count()); + } + + TextPos TextLines::Modify(TextPos start, TextPos end, const wchar_t* input) + { + return Modify(start, end, input, wcslen(input)); + } + + TextPos TextLines::Modify(TextPos start, TextPos end, const WString& input) + { + return Modify(start, end, input.Buffer(), input.Length()); + } + + void TextLines::Clear() + { + RemoveLines(0, lines.Count()); + TextLine line; + line.Initialize(); + lines.Add(line); + if (ownerElement) + { + ownerElement->InvokeOnElementStateChanged(); + } + } + + //-------------------------------------------------------- + + void TextLines::ClearMeasurement() + { + for (vint i = 0; i < lines.Count(); i++) + { + lines[i].availableOffsetCount = 0; + } + + tabWidth = tabSpaceCount * (charMeasurer ? charMeasurer->MeasureWidth({ L' ' }) : 1); + if (tabWidth == 0) + { + tabWidth = 1; + } + + if (ownerElement) + { + ownerElement->InvokeOnElementStateChanged(); + } + } + + vint TextLines::GetTabSpaceCount() + { + return tabSpaceCount; + } + + void TextLines::SetTabSpaceCount(vint value) + { + if(value<1) value=1; + if(tabSpaceCount!=value) + { + tabSpaceCount=value; + ClearMeasurement(); + } + } + + void TextLines::MeasureRow(vint row) + { + TextLine& line = lines[row]; + vint offset = 0; + if (line.availableOffsetCount) + { + offset = line.att[line.availableOffsetCount - 1].rightOffset; + } + for (vint i = line.availableOffsetCount; i < line.dataLength; i++) + { + CharAtt& att = line.att[i]; + wchar_t c = line.text[i]; + vint width = 0; + vint passwordWidth = 0; + if (passwordChar) + { + passwordWidth = charMeasurer ? charMeasurer->MeasureWidth({ passwordChar }) : 1; + } + + if (c == L'\t') + { + width = tabWidth - offset % tabWidth; + } +#if defined VCZH_MSVC + else if (UTF16SPFirst(c) && (i + 1 < line.dataLength) && UTF16SPSecond(line.text[i + 1])) + { + width = passwordChar ? passwordWidth : (charMeasurer ? charMeasurer->MeasureWidth({ c, line.text[i + 1] }) : 1); + offset += width; + att.rightOffset = (int)offset; + line.att[i + 1].rightOffset = (int)offset; + i++; + continue; + } +#endif + else + { + width = passwordChar ? passwordWidth : (charMeasurer ? charMeasurer->MeasureWidth({ c }) : 1); + } + offset += width; + att.rightOffset = (int)offset; + } + line.availableOffsetCount = line.dataLength; + } + + vint TextLines::GetRowWidth(vint row) + { + if(row<0 || row>=lines.Count()) return -1; + TextLine& line=lines[row]; + if(line.dataLength==0) + { + return 0; + } + else + { + MeasureRow(row); + return line.att[line.dataLength-1].rightOffset; + } + } + + vint TextLines::GetRowHeight() + { + return charMeasurer ? charMeasurer->GetRowHeight() : 1; + } + + vint TextLines::GetMaxWidth() + { + vint width=0; + for(vint i=0;i=h*lines.Count()) + { + point.y=h*lines.Count()-1; + } + + vint row=point.y/h; + if(point.x<0) + { + return TextPos(row, 0); + } + else if(point.x>=GetRowWidth(row)) + { + return TextPos(row, lines[row].dataLength); + } + TextLine& line=lines[row]; + + vint i1=0, i2=line.dataLength; + vint p1=0, p2=line.att[line.dataLength-1].rightOffset; + while(i2-i1>1) + { + vint i=(i1+i2)/2; + vint p=i==0?0:line.att[i-1].rightOffset; + if(point.x 0 && UTF16SPFirst(line.text[i1 - 1])) + { + i1--; + } +#endif + return TextPos(row, i1); + } + + Point TextLines::GetPointFromTextPos(TextPos pos) + { + if(IsAvailable(pos)) + { + vint y = pos.row * GetRowHeight(); + if(pos.column==0) + { + return Point(0, y); + } + else + { + MeasureRow(pos.row); + TextLine& line=lines[pos.row]; + return Point(line.att[pos.column-1].rightOffset, y); + } + } + else + { + return Point(-1, -1); + } + } + + Rect TextLines::GetRectFromTextPos(TextPos pos) + { + Point point=GetPointFromTextPos(pos); + if(point==Point(-1, -1)) + { + return Rect(-1, -1, -1, -1); + } + else + { + vint h = GetRowHeight(); + TextLine& line=lines[pos.row]; + if(pos.column==line.dataLength) + { + return Rect(point, Size(h/2, h)); + } + else + { + return Rect(point, Size(line.att[pos.column].rightOffset-point.x, h)); + } + } + } + + //-------------------------------------------------------- + + wchar_t TextLines::GetPasswordChar() + { + return passwordChar; + } + + void TextLines::SetPasswordChar(wchar_t value) + { + passwordChar=value; + ClearMeasurement(); + } + } + + using namespace text; + +/*********************************************************************** +GuiColorizedTextElement +***********************************************************************/ + + GuiColorizedTextElement::GuiColorizedTextElement() + :callback(0) + ,isVisuallyEnabled(true) + ,isFocused(false) + ,caretVisible(false) + ,lines(this) + { + } + + text::TextLines& GuiColorizedTextElement::GetLines() + { + return lines; + } + + GuiColorizedTextElement::ICallback* GuiColorizedTextElement::GetCallback() + { + return callback; + } + + void GuiColorizedTextElement::SetCallback(ICallback* value) + { + callback=value; + if(!callback) + { + lines.SetCharMeasurer(0); + } + } + + const GuiColorizedTextElement::ColorArray& GuiColorizedTextElement::GetColors() + { + return colors; + } + + void GuiColorizedTextElement::SetColors(const ColorArray& value) + { + CopyFrom(colors, value); + if(callback) callback->ColorChanged(); + InvokeOnElementStateChanged(); + } + + void GuiColorizedTextElement::ResetTextColorIndex(vint index) + { + vint lineCount = lines.GetCount(); + for (vint i = 0; i < lineCount; i++) + { + auto& line = lines.GetLine(i); + line.lexerFinalState = -1; + line.contextFinalState = -1; + for (vint j = 0; j < line.dataLength; j++) + { + line.att[j].colorIndex = (vuint32_t)index; + } + } + } + + const FontProperties& GuiColorizedTextElement::GetFont() + { + return font; + } + + void GuiColorizedTextElement::SetFont(const FontProperties& value) + { + if(font!=value) + { + font=value; + if(callback) + { + callback->FontChanged(); + } + InvokeOnElementStateChanged(); + } + } + + wchar_t GuiColorizedTextElement::GetPasswordChar() + { + return lines.GetPasswordChar(); + } + + void GuiColorizedTextElement::SetPasswordChar(wchar_t value) + { + if(lines.GetPasswordChar()!=value) + { + lines.SetPasswordChar(value); + InvokeOnElementStateChanged(); + } + } + + Point GuiColorizedTextElement::GetViewPosition() + { + return viewPosition; + } + + void GuiColorizedTextElement::SetViewPosition(Point value) + { + if(viewPosition!=value) + { + viewPosition=value; + InvokeOnElementStateChanged(); + } + } + + bool GuiColorizedTextElement::GetVisuallyEnabled() + { + return isVisuallyEnabled; + } + + void GuiColorizedTextElement::SetVisuallyEnabled(bool value) + { + if(isVisuallyEnabled!=value) + { + isVisuallyEnabled=value; + InvokeOnElementStateChanged(); + } + } + + bool GuiColorizedTextElement::GetFocused() + { + return isFocused; + } + + void GuiColorizedTextElement::SetFocused(bool value) + { + if(isFocused!=value) + { + isFocused=value; + InvokeOnElementStateChanged(); + } + } + + TextPos GuiColorizedTextElement::GetCaretBegin() + { + return caretBegin; + } + + void GuiColorizedTextElement::SetCaretBegin(TextPos value) + { + caretBegin=value; + InvokeOnElementStateChanged(); + } + + TextPos GuiColorizedTextElement::GetCaretEnd() + { + return caretEnd; + } + + void GuiColorizedTextElement::SetCaretEnd(TextPos value) + { + caretEnd=value; + InvokeOnElementStateChanged(); + } + + bool GuiColorizedTextElement::GetCaretVisible() + { + return caretVisible; + } + + void GuiColorizedTextElement::SetCaretVisible(bool value) + { + caretVisible=value; + InvokeOnElementStateChanged(); + } + + Color GuiColorizedTextElement::GetCaretColor() + { + return caretColor; + } + + void GuiColorizedTextElement::SetCaretColor(Color value) + { + if(caretColor!=value) + { + caretColor=value; + InvokeOnElementStateChanged(); + } + } + } + } +} + + +/*********************************************************************** +.\NATIVEWINDOW\GUINATIVEWINDOW.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + +/*********************************************************************** +INativeWindowListener +***********************************************************************/ + + INativeWindowListener::HitTestResult INativeWindowListener::HitTest(Point location) + { + return INativeWindowListener::NoDecision; + } + + void INativeWindowListener::Moving(Rect& bounds, bool fixSizeOnly) + { + } + + void INativeWindowListener::Moved() + { + } + + void INativeWindowListener::Enabled() + { + } + + void INativeWindowListener::Disabled() + { + } + + void INativeWindowListener::GotFocus() + { + } + + void INativeWindowListener::LostFocus() + { + } + + void INativeWindowListener::Activated() + { + } + + void INativeWindowListener::Deactivated() + { + } + + void INativeWindowListener::Opened() + { + } + + void INativeWindowListener::Closing(bool& cancel) + { + } + + void INativeWindowListener::Closed() + { + } + + void INativeWindowListener::Paint() + { + } + + void INativeWindowListener::Destroying() + { + } + + void INativeWindowListener::Destroyed() + { + } + + void INativeWindowListener::LeftButtonDown(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::LeftButtonUp(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::LeftButtonDoubleClick(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::RightButtonDown(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::RightButtonUp(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::RightButtonDoubleClick(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::MiddleButtonDown(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::MiddleButtonUp(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::MiddleButtonDoubleClick(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::HorizontalWheel(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::VerticalWheel(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::MouseMoving(const NativeWindowMouseInfo& info) + { + } + + void INativeWindowListener::MouseEntered() + { + } + + void INativeWindowListener::MouseLeaved() + { + } + + void INativeWindowListener::KeyDown(const NativeWindowKeyInfo& info) + { + } + + void INativeWindowListener::KeyUp(const NativeWindowKeyInfo& info) + { + } + + void INativeWindowListener::SysKeyDown(const NativeWindowKeyInfo& info) + { + } + + void INativeWindowListener::SysKeyUp(const NativeWindowKeyInfo& info) + { + } + + void INativeWindowListener::Char(const NativeWindowCharInfo& info) + { + } + +/*********************************************************************** +INativeControllerListener +***********************************************************************/ + + void INativeControllerListener::LeftButtonDown(Point position) + { + } + + void INativeControllerListener::LeftButtonUp(Point position) + { + } + + void INativeControllerListener::RightButtonDown(Point position) + { + } + + void INativeControllerListener::RightButtonUp(Point position) + { + } + + void INativeControllerListener::MouseMoving(Point position) + { + } + + void INativeControllerListener::GlobalTimer() + { + } + + void INativeControllerListener::ClipboardUpdated() + { + } + + void INativeControllerListener::NativeWindowCreated(INativeWindow* window) + { + } + + void INativeControllerListener::NativeWindowDestroying(INativeWindow* window) + { + } + +/*********************************************************************** +Native Window Provider +***********************************************************************/ + + INativeController* currentController=0; + + INativeController* GetCurrentController() + { + return currentController; + } + + void SetCurrentController(INativeController* controller) + { + currentController=controller; + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENT.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + using namespace parsing::tabling; + using namespace parsing::xml; + using namespace regex; + using namespace stream; + +/*********************************************************************** +DocumentFontSize +***********************************************************************/ + + DocumentFontSize DocumentFontSize::Parse(const WString& value) + { + if (value.Length() > 0 && value[value.Length() - 1] == L'x') + { + return DocumentFontSize(wtof(value.Left(value.Length() - 1)), true); + } + else + { + return DocumentFontSize(wtof(value), false); + } + } + + WString DocumentFontSize::ToString()const + { + return ftow(size) + (relative ? L"x" : L""); + } + +/*********************************************************************** +DocumentImageRun +***********************************************************************/ + + const wchar_t* DocumentImageRun::RepresentationText=L"[Image]"; + const wchar_t* DocumentEmbeddedObjectRun::RepresentationText=L"[EmbeddedObject]"; + +/*********************************************************************** +ExtractTextVisitor +***********************************************************************/ + + namespace document_operation_visitors + { + class ExtractTextVisitor : public Object, public DocumentRun::IVisitor + { + public: + stream::TextWriter& writer; + bool skipNonTextContent; + + ExtractTextVisitor(stream::TextWriter& _writer, bool _skipNonTextContent) + :writer(_writer) + ,skipNonTextContent(_skipNonTextContent) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + FOREACH(Ptr, subRun, run->runs) + { + subRun->Accept(this); + } + } + + void VisitContent(DocumentContentRun* run) + { + writer.WriteString(run->GetRepresentationText()); + } + + void Visit(DocumentTextRun* run)override + { + VisitContent(run); + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + if(!skipNonTextContent) + { + VisitContent(run); + } + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + if(!skipNonTextContent) + { + VisitContent(run); + } + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + +/*********************************************************************** +DocumentParagraphRun +***********************************************************************/ + + WString DocumentParagraphRun::GetText(bool skipNonTextContent) + { + return GenerateToStream([&](StreamWriter& writer) + { + GetText(writer, skipNonTextContent); + }); + } + + void DocumentParagraphRun::GetText(stream::TextWriter& writer, bool skipNonTextContent) + { + ExtractTextVisitor visitor(writer, skipNonTextContent); + Accept(&visitor); + } + +/*********************************************************************** +DocumentModel +***********************************************************************/ + + const wchar_t* DocumentModel::DefaultStyleName = L"#Default"; + const wchar_t* DocumentModel::SelectionStyleName = L"#Selection"; + const wchar_t* DocumentModel::ContextStyleName = L"#Context"; + const wchar_t* DocumentModel::NormalLinkStyleName = L"#NormalLink"; + const wchar_t* DocumentModel::ActiveLinkStyleName = L"#ActiveLink"; + + DocumentModel::DocumentModel() + { + { + FontProperties font=GetCurrentController()->ResourceService()->GetDefaultFont(); + Ptr sp=new DocumentStyleProperties; + sp->face=font.fontFamily; + sp->size=DocumentFontSize((double)font.size, false); + sp->color=Color(); + sp->backgroundColor=Color(0, 0, 0, 0); + sp->bold=font.bold; + sp->italic=font.italic; + sp->underline=font.underline; + sp->strikeline=font.strikeline; + sp->antialias=font.antialias; + sp->verticalAntialias=font.verticalAntialias; + + Ptr style=new DocumentStyle; + style->styles=sp; + styles.Add(L"#Default", style); + } + { + Ptr sp=new DocumentStyleProperties; + sp->color=Color(255, 255, 255); + sp->backgroundColor=Color(51, 153, 255); + + Ptr style=new DocumentStyle; + style->styles=sp; + styles.Add(L"#Selection", style); + } + { + Ptr sp=new DocumentStyleProperties; + + Ptr style=new DocumentStyle; + style->styles=sp; + styles.Add(L"#Context", style); + } + { + Ptr sp=new DocumentStyleProperties; + sp->color=Color(0, 0, 255); + sp->underline=true; + + Ptr style=new DocumentStyle; + style->parentStyleName=L"#Context"; + style->styles=sp; + styles.Add(L"#NormalLink", style); + } + { + Ptr sp=new DocumentStyleProperties; + sp->color=Color(255, 128, 0); + sp->underline=true; + + Ptr style=new DocumentStyle; + style->parentStyleName=L"#Context"; + style->styles=sp; + styles.Add(L"#ActiveLink", style); + } + } + + void DocumentModel::MergeStyle(Ptr style, Ptr parent) + { + if(!style->face && parent->face) style->face =parent->face; + if(!style->size && parent->size) style->size =parent->size; + if(!style->color && parent->color) style->color =parent->color; + if(!style->backgroundColor && parent->backgroundColor) style->backgroundColor =parent->backgroundColor; + if(!style->bold && parent->bold) style->bold =parent->bold; + if(!style->italic && parent->italic) style->italic =parent->italic; + if(!style->underline && parent->underline) style->underline =parent->underline; + if(!style->strikeline && parent->strikeline) style->strikeline =parent->strikeline; + if(!style->antialias && parent->antialias) style->antialias =parent->antialias; + if(!style->verticalAntialias && parent->verticalAntialias) style->verticalAntialias =parent->verticalAntialias; + } + + void DocumentModel::MergeBaselineStyle(Ptr style, const WString& styleName) + { + auto indexDst = styles.Keys().IndexOf(styleName); + Ptr sp = new DocumentStyleProperties; + MergeStyle(sp, style); + if (indexDst != -1) + { + MergeStyle(sp, styles.Values()[indexDst]->styles); + } + + if (indexDst == -1) + { + auto style = new DocumentStyle; + style->styles = sp; + styles.Add(styleName, style); + } + else + { + styles.Values()[indexDst]->styles = sp; + } + + FOREACH(Ptr, style, styles.Values()) + { + style->resolvedStyles = nullptr; + } + } + + void DocumentModel::MergeBaselineStyle(Ptr baselineDocument, const WString& styleName) + { + auto indexSrc = baselineDocument->styles.Keys().IndexOf(styleName + L"-Override"); + if (indexSrc == -1) + { + return; + } + + auto csp = baselineDocument->styles.Values()[indexSrc]->styles; + MergeBaselineStyle(csp, styleName); + } + + void DocumentModel::MergeBaselineStyles(Ptr baselineDocument) + { + MergeBaselineStyle(baselineDocument, DefaultStyleName); + MergeBaselineStyle(baselineDocument, SelectionStyleName); + MergeBaselineStyle(baselineDocument, ContextStyleName); + MergeBaselineStyle(baselineDocument, NormalLinkStyleName); + MergeBaselineStyle(baselineDocument, ActiveLinkStyleName); + } + + void DocumentModel::MergeDefaultFont(const FontProperties& defaultFont) + { + Ptr style = new DocumentStyleProperties; + + style->face =defaultFont.fontFamily; + style->size =DocumentFontSize((double)defaultFont.size, false); + style->bold =defaultFont.bold; + style->italic =defaultFont.italic; + style->underline =defaultFont.underline; + style->strikeline =defaultFont.strikeline; + style->antialias =defaultFont.antialias; + style->verticalAntialias =defaultFont.verticalAntialias; + + MergeBaselineStyle(style, DefaultStyleName); + } + + DocumentModel::ResolvedStyle DocumentModel::GetStyle(Ptr sp, const ResolvedStyle& context) + { + FontProperties font; + font.fontFamily =sp->face ?sp->face.Value() :context.style.fontFamily; + font.bold =sp->bold ?sp->bold.Value() :context.style.bold; + font.italic =sp->italic ?sp->italic.Value() :context.style.italic; + font.underline =sp->underline ?sp->underline.Value() :context.style.underline; + font.strikeline =sp->strikeline ?sp->strikeline.Value() :context.style.strikeline; + font.antialias =sp->antialias ?sp->antialias.Value() :context.style.antialias; + font.verticalAntialias =sp->verticalAntialias ?sp->verticalAntialias.Value() :context.style.verticalAntialias; + Color color =sp->color ?sp->color.Value() :context.color; + Color backgroundColor =sp->backgroundColor ?sp->backgroundColor.Value() :context.backgroundColor; + + if (sp->size) + { + font.size = (vint)(sp->size.Value().relative ? context.style.size * sp->size.Value().size : sp->size.Value().size); + } + else + { + font.size = context.style.size; + } + return ResolvedStyle(font, color, backgroundColor); + } + + DocumentModel::ResolvedStyle DocumentModel::GetStyle(const WString& styleName, const ResolvedStyle& context) + { + Ptr selectedStyle; + { + vint index=styles.Keys().IndexOf(styleName); + if(index!=-1) + { + selectedStyle=styles.Values()[index]; + } + else + { + selectedStyle=styles[L"#Default"]; + } + } + + if(!selectedStyle->resolvedStyles) + { + Ptr sp = new DocumentStyleProperties; + selectedStyle->resolvedStyles = sp; + + Ptr currentStyle; + WString currentName = styleName; + while(true) + { + vint index = styles.Keys().IndexOf(currentName); + if (index == -1) break; + currentStyle = styles.Values().Get(index); + currentName = currentStyle->parentStyleName; + MergeStyle(sp, currentStyle->styles); + } + } + + Ptr sp=selectedStyle->resolvedStyles; + return GetStyle(sp, context); + } + + WString DocumentModel::GetText(bool skipNonTextContent) + { + return GenerateToStream([&](StreamWriter& writer) + { + GetText(writer, skipNonTextContent); + }); + } + + void DocumentModel::GetText(stream::TextWriter& writer, bool skipNonTextContent) + { + for(vint i=0;i paragraph=paragraphs[i]; + paragraph->GetText(writer, skipNonTextContent); + if(i, childRun, run->runs) + { + childRun->Accept(this); + } + } + + void Visit(DocumentTextRun* run)override + { + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + + class ModifyDocumentForClipboardVisitor : public TraverseDocumentVisitor + { + public: + ModifyDocumentForClipboardVisitor() + { + } + + void VisitContainer(DocumentContainerRun* run)override + { + for (vint i = run->runs.Count() - 1; i >= 0; i--) + { + auto childRun = run->runs[i]; + if (childRun.Cast()) + { + run->runs.RemoveAt(i); + } + } + TraverseDocumentVisitor::VisitContainer(run); + } + }; + + class CollectImageRunsVisitor : public TraverseDocumentVisitor + { + public: + List> imageRuns; + + CollectImageRunsVisitor() + { + } + + void Visit(DocumentImageRun* run)override + { + run->source = L"res://Image_" + itow(imageRuns.Count()); + imageRuns.Add(run); + } + }; + } + using namespace document_clipboard_visitors; + + void ModifyDocumentForClipboard(Ptr model) + { + ModifyDocumentForClipboardVisitor visitor; + FOREACH(Ptr, paragraph, model->paragraphs) + { + paragraph->Accept(&visitor); + } + } + + Ptr LoadDocumentFromClipboardStream(stream::IStream& stream) + { + auto tempResource = MakePtr(); + auto tempResourceItem = MakePtr(); + tempResource->AddItem(L"Document", tempResourceItem); + auto tempResolver = MakePtr(tempResource, L""); + + { + vint32_t count = 0; + if (stream.Read(&count, sizeof(count)) != sizeof(count)) return nullptr; + for (vint i = 0; i < count; i++) + { + vint32_t size = 0; + if (stream.Read(&size, sizeof(size)) != sizeof(size)) return nullptr; + if (size > 0) + { + Array buffer(size); + if (stream.Read(&buffer[0], size) != size) return nullptr; + if (auto image = GetCurrentController()->ImageService()->CreateImageFromMemory(&buffer[0], buffer.Count())) + { + auto imageItem = MakePtr(); + imageItem->SetContent(L"Image", MakePtr(image, 0)); + tempResource->AddItem(L"Image_" + itow(i), imageItem); + } + } + } + } + + StreamReader streamReader(stream); + auto text = streamReader.ReadToEnd(); + List errors; + + auto parser = GetParserManager()->GetParser(L"XML"); + auto xml = parser->Parse({}, text, errors); + if (errors.Count() > 0) return nullptr; + + auto document = DocumentModel::LoadFromXml(tempResourceItem, xml, tempResolver, errors); + return document; + } + + void SaveDocumentToClipboardStream(Ptr model, stream::IStream& stream) + { + CollectImageRunsVisitor visitor; + FOREACH(Ptr, paragraph, model->paragraphs) + { + paragraph->Accept(&visitor); + } + { + vint32_t count = (vint32_t)visitor.imageRuns.Count(); + stream.Write(&count, sizeof(count)); + FOREACH(Ptr, imageRun, visitor.imageRuns) + { + stream::MemoryStream memoryStream; + if (imageRun->image) + { + imageRun->image->SaveToStream(memoryStream); + } + + count = (vint32_t)memoryStream.Size(); + stream.Write(&count, sizeof(count)); + if (count > 0) + { + stream.Write(memoryStream.GetInternalBuffer(), count); + } + } + } + + StreamWriter streamWriter(stream); + auto xml = model->SaveToXml(); + XmlPrint(xml, streamWriter); + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTCLIPBOARD_HTMLFORMAT.CPP ***********************************************************************/ namespace vl @@ -408,248 +32316,4382 @@ namespace vl { using namespace collections; using namespace stream; - using namespace parsing::xml; - using namespace reflection::description; - using namespace controls; -/*********************************************************************** -Class Name Record (ClassNameRecord) -***********************************************************************/ - - class GuiResourceClassNameRecordTypeResolver - : public Object - , public IGuiResourceTypeResolver - , private IGuiResourceTypeResolver_DirectLoadStream + namespace document_clipboard_visitors { - public: - WString GetType()override - { - return L"ClassNameRecord"; - } - - bool XmlSerializable()override - { - return false; - } - - bool StreamSerializable()override - { - return true; - } - - IGuiResourceTypeResolver_DirectLoadStream* DirectLoadStream()override - { - return this; - } - - void SerializePrecompiled(Ptr resource, Ptr content, stream::IStream& stream)override - { - if (auto obj = content.Cast()) - { - internal::ContextFreeWriter writer(stream); - writer << obj->classNames; - } - } - - Ptr ResolveResourcePrecompiled(Ptr resource, stream::IStream& stream, GuiResourceError::List& errors)override - { - internal::ContextFreeReader reader(stream); - - auto obj = MakePtr(); - reader << obj->classNames; - return obj; - } - }; - -/*********************************************************************** -IGuiInstanceResourceManager -***********************************************************************/ - - IGuiResourceManager* resourceManager = nullptr; - - IGuiResourceManager* GetResourceManager() - { - return resourceManager; - } - - class GuiResourceManager : public Object, public IGuiResourceManager, public IGuiPlugin - { - protected: - typedef Dictionary> ResourceMap; - - List> anonymousResources; - ResourceMap resources; - ResourceMap instanceResources; - - class PendingResource : public Object + class GenerateHtmlVisitor : public Object, public DocumentRun::IVisitor { + typedef DocumentModel::ResolvedStyle ResolvedStyle; public: - Ptr metadata; - GuiResourceUsage usage; - MemoryStream memoryStream; - SortedList dependencies; + List styles; + DocumentModel* model; + StreamWriter& writer; - Ptr LoadResource() + GenerateHtmlVisitor(DocumentModel* _model, StreamWriter& _writer) + :model(_model) + , writer(_writer) { - memoryStream.SeekFromBegin(0); - List errors; - auto resource = GuiResource::LoadPrecompiledBinary(memoryStream, errors); - CHECK_ERROR(errors.Count() == 0, L"PendingResource::LoadResource()#Failed to load the resource."); - return resource; + ResolvedStyle style; + style.color = Color(0, 0, 0, 0); + style.backgroundColor = Color(0, 0, 0, 0); + style = model->GetStyle(DocumentModel::DefaultStyleName, style); + styles.Add(style); + } + + void VisitContainer(DocumentContainerRun* run) + { + FOREACH(Ptr, subRun, run->runs) + { + subRun->Accept(this); + } + } + + WString ColorToString(Color c) + { + auto result = c.ToString(); + if (result.Length() == 9) result = result.Left(7); + return result; + } + + void Visit(DocumentTextRun* run)override + { + WString text = run->GetRepresentationText(); + if (text.Length() > 0) + { + ResolvedStyle style = styles[styles.Count() - 1]; + + writer.WriteString(L""); + + for (vint i = 0; i < text.Length(); i++) + { + switch (wchar_t c = text[i]) + { + case L'&': writer.WriteString(L"&"); break; + case L'<': writer.WriteString(L"<"); break; + case L'>': writer.WriteString(L">"); break; + case L'\r': break; + case L'\n': writer.WriteString(L"
"); break; + case L' ': writer.WriteString(L" "); break; + case L'\t': writer.WriteString(L"
\t
"); break; + default: writer.WriteChar(c); break; + } + } + + writer.WriteString(L"
"); + } + } + + void Visit(DocumentStylePropertiesRun* run)override + { + ResolvedStyle style = styles[styles.Count() - 1]; + style = model->GetStyle(run->style, style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count() - 1); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + ResolvedStyle style = styles[styles.Count() - 1]; + style = model->GetStyle(run->styleName, style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count() - 1); + } + + void Visit(DocumentHyperlinkRun* run)override + { + writer.WriteString(L"reference.Length(); i++) + { + switch (wchar_t c = run->reference[i]) + { + case L'&': writer.WriteString(L"&"); break; + case L'<': writer.WriteString(L"<"); break; + case L'>': writer.WriteString(L">"); break; + case L'"': writer.WriteString(L"""); break; + case L'\'': writer.WriteString(L"'"); break; + default: writer.WriteChar(c); break; + } + } + writer.WriteString(L"\">"); + + ResolvedStyle style = styles[styles.Count() - 1]; + style = model->GetStyle((run->normalStyleName == L"" ? DocumentModel::NormalLinkStyleName : run->normalStyleName), style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count() - 1); + + writer.WriteString(L""); + } + + void Visit(DocumentImageRun* run)override + { + if (run->image) + { + writer.WriteString(L"size.x) + L"\" height=\"" + itow(run->size.y) + L"\" src=\"data:image/"); + switch (run->image->GetFormat()) + { + case INativeImage::Bmp: writer.WriteString(L"bmp;base64,"); break; + case INativeImage::Gif: writer.WriteString(L"gif;base64,"); break; + case INativeImage::Icon: writer.WriteString(L"icon;base64,"); break; + case INativeImage::Jpeg: writer.WriteString(L"jpeg;base64,"); break; + case INativeImage::Png: writer.WriteString(L"png;base64,"); break; + case INativeImage::Tiff: writer.WriteString(L"tiff;base64,"); break; + case INativeImage::Wmp: writer.WriteString(L"wmp;base64,"); break; + default: writer.WriteString(L"unsupported;base64,\"/>"); return; + } + + MemoryStream memoryStream; + run->image->SaveToStream(memoryStream); + memoryStream.SeekFromBegin(0); + while (true) + { + vuint8_t bytes[3] = { 0,0,0 }; + vint read = memoryStream.Read(&bytes, sizeof(bytes)); + if (read == 0) break; + + vuint8_t b1 = bytes[0] / (1 << 2); + vuint8_t b2 = ((bytes[0] % (1 << 2)) << 4) + bytes[1] / (1 << 4); + vuint8_t b3 = ((bytes[1] % (1 << 4)) << 2) + bytes[2] / (1 << 6); + vuint8_t b4 = bytes[2] % (1 << 6); + + const wchar_t* BASE64 = + L"ABCDEFG" + L"HIJKLMN" + L"OPQRST" + L"UVWXYZ" + L"abcdefg" + L"hijklmn" + L"opqrst" + L"uvwxyz" + L"0123456789" + L"+/"; +#define BASE64_CHAR(b) BASE64[b] + switch (read) + { + case 1: + writer.WriteChar(BASE64_CHAR(b1)); + writer.WriteChar(BASE64_CHAR(b2)); + writer.WriteChar(L'='); + writer.WriteChar(L'='); + break; + case 2: + writer.WriteChar(BASE64_CHAR(b1)); + writer.WriteChar(BASE64_CHAR(b2)); + writer.WriteChar(BASE64_CHAR(b3)); + writer.WriteChar(L'='); + break; + case 3: + writer.WriteChar(BASE64_CHAR(b1)); + writer.WriteChar(BASE64_CHAR(b2)); + writer.WriteChar(BASE64_CHAR(b3)); + writer.WriteChar(BASE64_CHAR(b4)); + break; + } +#undef BASE64_CHAR + } + + writer.WriteString(L"\"/>"); + } + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); } }; - Group> depToPendings; - SortedList> pendingResources; + } + using namespace document_clipboard_visitors; - public: +#define HTML_LINE(LINE) LINE "\r\n" - GUI_PLUGIN_NAME(GacUI_Res_Resource) + void SaveDocumentToHtmlUtf8(Ptr model, AString& header, AString& content, AString& footer) + { + header = + HTML_LINE("") + HTML_LINE("") + HTML_LINE("
") + HTML_LINE("GacUI Document 1.0") + HTML_LINE("") + HTML_LINE("
") + HTML_LINE("") + ; + + MemoryStream memoryStream; + { + Utf8Encoder encoder; + EncoderStream encoderStream(memoryStream, encoder); + StreamWriter writer(encoderStream); + GenerateHtmlVisitor visitor(model.Obj(), writer); + + FOREACH(Ptr, paragraph, model->paragraphs) + { + writer.WriteString(L"

alignment) + { + switch (paragraph->alignment.Value()) + { + case Alignment::Left: writer.WriteString(L"left;"); break; + case Alignment::Center: writer.WriteString(L"center;"); break; + case Alignment::Right: writer.WriteString(L"right;"); break; + } + } + else + { + writer.WriteString(L"left;"); + } + writer.WriteString(L"\">"); + paragraph->Accept(&visitor); + writer.WriteString(L"

\r\n"); + } + } + char zero = 0; + memoryStream.Write(&zero, sizeof(zero)); + content = (const char*)memoryStream.GetInternalBuffer(); + + footer = + HTML_LINE("") + HTML_LINE("") + ; + } + + void SaveDocumentToHtmlClipboardStream(Ptr model, stream::IStream& stream) + { + AString header, content, footer; + SaveDocumentToHtmlUtf8(model, header, content, footer); + + char clipboardHeader[] = + HTML_LINE("StartHTML:-1") + HTML_LINE("EndHTML:-1") + HTML_LINE("StartFragment:0000000000") + HTML_LINE("EndFragment:0000000000") + ; + char commentStart[] = ""; + char commentEnd[] = ""; + vint offsetStart = sizeof(clipboardHeader) - 1 + header.Length() + sizeof(commentStart) - 1; + vint offsetEnd = offsetStart + content.Length(); + + AString offsetStartString = itoa(offsetStart); + AString offsetEndString = itoa(offsetEnd); + memcpy(strstr(clipboardHeader, "EndFragment:") - offsetStartString.Length() - 2, offsetStartString.Buffer(), offsetStartString.Length()); + memcpy(clipboardHeader + sizeof(clipboardHeader) - 1 - offsetEndString.Length() - 2, offsetEndString.Buffer(), offsetEndString.Length()); + + stream.Write(clipboardHeader, sizeof(clipboardHeader) - 1); + if (header.Length() > 0) stream.Write((void*)header.Buffer(), header.Length()); + stream.Write(commentStart, sizeof(commentStart) - 1); + if (content.Length() > 0) stream.Write((void*)content.Buffer(), content.Length()); + stream.Write(commentEnd, sizeof(commentEnd) - 1); + if (footer.Length() > 0) stream.Write((void*)footer.Buffer(), footer.Length()); + } + +#undef HTML_LINE + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTCLIPBOARD_RICHTEXTFORMAT.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + using namespace stream; + + namespace document_clipboard_visitors + { + class GenerateRtfVisitor : public Object, public DocumentRun::IVisitor + { + typedef DocumentModel::ResolvedStyle ResolvedStyle; + public: + List styles; + DocumentModel* model; + StreamWriter& writer; + + List& fontTable; + List& colorTable; + Dictionary fontIndex; + Dictionary colorIndex; + + vint GetFont(const WString& fontName) + { + vint index = fontIndex.Keys().IndexOf(fontName); + if (index == -1) + { + index = fontTable.Add(fontName); + fontIndex.Add(fontName, index); + return index; + } + return fontIndex.Values()[index]; + } + + vint GetColor(Color color) + { + if (color.a == 0) return 0; + vint index = colorIndex.Keys().IndexOf(color); + if (index == -1) + { + index = colorTable.Add(color) + 1; + colorIndex.Add(color, index); + return index; + } + return colorIndex.Values()[index]; + } + + GenerateRtfVisitor(DocumentModel* _model, List& _fontTable, List& _colorTable, StreamWriter& _writer) + :model(_model) + , writer(_writer) + , fontTable(_fontTable) + , colorTable(_colorTable) + { + ResolvedStyle style; + style.color = Color(0, 0, 0, 0); + style.backgroundColor = Color(0, 0, 0, 0); + style = model->GetStyle(DocumentModel::DefaultStyleName, style); + styles.Add(style); + } + + void VisitContainer(DocumentContainerRun* run) + { + FOREACH(Ptr, subRun, run->runs) + { + subRun->Accept(this); + } + } + + void Visit(DocumentTextRun* run)override + { + WString text = run->GetRepresentationText(); + if (text.Length() > 0) + { + ResolvedStyle style = styles[styles.Count() - 1]; + + writer.WriteString(L"{\\f" + itow(GetFont(style.style.fontFamily))); + writer.WriteString(L"\\fs" + itow((vint)(style.style.size * 1.5))); + writer.WriteString(L"\\cf" + itow(GetColor(style.color))); + writer.WriteString(L"\\cb" + itow(GetColor(style.backgroundColor))); + writer.WriteString(L"\\chshdng" + itow(GetColor(style.backgroundColor))); + writer.WriteString(L"\\chcbpat" + itow(GetColor(style.backgroundColor))); + + if (style.style.bold) writer.WriteString(L"\\b"); + if (style.style.italic) writer.WriteString(L"\\i"); + if (style.style.underline) writer.WriteString(L"\\ul"); + if (style.style.strikeline) writer.WriteString(L"\\strike"); + + for (vint i = 0; i < text.Length(); i++) + { + writer.WriteString(L"\\u" + itow(text[i])); + } + + writer.WriteString(L"}"); + } + } + + void Visit(DocumentStylePropertiesRun* run)override + { + ResolvedStyle style = styles[styles.Count() - 1]; + style = model->GetStyle(run->style, style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count() - 1); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + ResolvedStyle style = styles[styles.Count() - 1]; + style = model->GetStyle(run->styleName, style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count() - 1); + } + + void Visit(DocumentHyperlinkRun* run)override + { + ResolvedStyle style = styles[styles.Count() - 1]; + style = model->GetStyle((run->normalStyleName == L"" ? DocumentModel::NormalLinkStyleName : run->normalStyleName), style); + styles.Add(style); + VisitContainer(run); + styles.RemoveAt(styles.Count() - 1); + } + + void Visit(DocumentImageRun* run)override + { + if (run->image) + { + writer.WriteString(L"{\\pict\\pngblip"); + writer.WriteString(L"\\picw" + itow(run->size.x) + L"\\pich" + itow(run->size.y)); + writer.WriteString(L"\\picwgoal" + itow(run->size.x * 15) + L"\\pichgoal" + itow(run->size.y * 15) + L" "); + + MemoryStream memoryStream; + run->image->SaveToStream(memoryStream, INativeImage::Png); + vint count = (vint)memoryStream.Size(); + vuint8_t* buffer = (vuint8_t*)memoryStream.GetInternalBuffer(); + for (vint i = 0; i < count; i++) + { + writer.WriteChar(L"0123456789abcdef"[buffer[i] / 16]); + writer.WriteChar(L"0123456789abcdef"[buffer[i] % 16]); + } + + writer.WriteString(L"}"); + } + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_clipboard_visitors; + + void SaveDocumentToRtf(Ptr model, AString& rtf) + { + List fontTable; + List colorTable; + MemoryStream bodyStream; + { + StreamWriter writer(bodyStream); + GenerateRtfVisitor visitor(model.Obj(), fontTable, colorTable, writer); + + FOREACH(Ptr, paragraph, model->paragraphs) + { + if (paragraph->alignment) + { + switch (paragraph->alignment.Value()) + { + case Alignment::Left: writer.WriteString(L"\\ql{"); break; + case Alignment::Center: writer.WriteString(L"\\qc{"); break; + case Alignment::Right: writer.WriteString(L"\\qr{"); break; + } + } + else + { + writer.WriteString(L"\\ql{"); + } + paragraph->Accept(&visitor); + writer.WriteString(L"}\\par"); + } + } + + MemoryStream rtfStream; + { + Utf8Encoder encoder; + EncoderStream encoderStream(rtfStream, encoder); + StreamWriter writer(encoderStream); + + writer.WriteString(L"{\\rtf1\\ansi\\deff0{\\fonttbl"); + FOREACH_INDEXER(WString, fontName, index, fontTable) + { + writer.WriteString(L"{\\f"); + writer.WriteString(itow(index)); + writer.WriteString(L" "); + writer.WriteString(fontName); + writer.WriteString(L";}"); + } + + writer.WriteString(L"}{\\colortbl"); + FOREACH_INDEXER(Color, color, index, colorTable) + { + writer.WriteString(L";\\red"); + writer.WriteString(itow(color.r)); + writer.WriteString(L"\\green"); + writer.WriteString(itow(color.g)); + writer.WriteString(L"\\blue"); + writer.WriteString(itow(color.b)); + } + + writer.WriteString(L";}{\\*\\generator GacUI Document 1.0}\\uc0"); + { + bodyStream.SeekFromBegin(0); + StreamReader reader(bodyStream); + writer.WriteString(reader.ReadToEnd()); + } + writer.WriteString(L"}"); + } + char zero = 0; + rtfStream.Write(&zero, sizeof(zero)); + rtf = (const char*)rtfStream.GetInternalBuffer(); + } + + void SaveDocumentToRtfStream(Ptr model, stream::IStream& stream) + { + AString rtf; + SaveDocumentToRtf(model, rtf); + stream.Write((void*)rtf.Buffer(), rtf.Length()); + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_ADDCONTAINER.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + using namespace document_editor; + +/*********************************************************************** +Insert container runs on top of all text ranges that intersect with the specified range +AddStyleVisitor : Apply a style on the specified range +AddHyperlinkVisitor : Apply a hyperlink on the specified range +AddStyleNameVisitor : Apply a style name on the specified range +***********************************************************************/ + + namespace document_operation_visitors + { + class AddContainerVisitor : public Object, public DocumentRun::IVisitor + { + public: + RunRangeMap& runRanges; + vint start; + vint end; + bool insertStyle; + + virtual Ptr CreateContainer() = 0; + + AddContainerVisitor(RunRangeMap& _runRanges, vint _start, vint _end) + :runRanges(_runRanges) + , start(_start) + , end(_end) + , insertStyle(false) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + for (vint i = run->runs.Count() - 1; i >= 0; i--) + { + Ptr subRun = run->runs[i]; + RunRange range = runRanges[subRun.Obj()]; + if (range.startAccept(this); + if (insertStyle) + { + Ptr containerRun = CreateContainer(); + run->runs.RemoveAt(i); + containerRun->runs.Add(subRun); + run->runs.Insert(i, containerRun); + } + } + } + insertStyle = false; + } + + void Visit(DocumentTextRun* run)override + { + insertStyle = true; + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + insertStyle = false; + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + insertStyle = false; + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + + class AddStyleVisitor : public AddContainerVisitor + { + public: + Ptr style; + + Ptr CreateContainer()override + { + Ptr containerRun = new DocumentStylePropertiesRun; + containerRun->style = CopyStyle(style); + return containerRun; + } + + AddStyleVisitor(RunRangeMap& _runRanges, vint _start, vint _end, Ptr _style) + :AddContainerVisitor(_runRanges, _start, _end) + , style(_style) + { + } + }; + + class AddHyperlinkVisitor : public AddContainerVisitor + { + public: + WString reference; + WString normalStyleName; + WString activeStyleName; + + Ptr CreateContainer()override + { + Ptr containerRun = new DocumentHyperlinkRun; + containerRun->reference = reference; + containerRun->normalStyleName = normalStyleName; + containerRun->activeStyleName = activeStyleName; + containerRun->styleName = normalStyleName; + return containerRun; + } + + AddHyperlinkVisitor(RunRangeMap& _runRanges, vint _start, vint _end, const WString& _reference, const WString& _normalStyleName, const WString& _activeStyleName) + :AddContainerVisitor(_runRanges, _start, _end) + , reference(_reference) + , normalStyleName(_normalStyleName) + , activeStyleName(_activeStyleName) + { + } + }; + + class AddStyleNameVisitor : public AddContainerVisitor + { + public: + WString styleName; + + Ptr CreateContainer()override + { + Ptr containerRun = new DocumentStyleApplicationRun; + containerRun->styleName = styleName; + return containerRun; + } + + AddStyleNameVisitor(RunRangeMap& _runRanges, vint _start, vint _end, const WString& _styleName) + :AddContainerVisitor(_runRanges, _start, _end) + , styleName(_styleName) + { + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + void AddStyle(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end, Ptr style) + { + AddStyleVisitor visitor(runRanges, start, end, style); + run->Accept(&visitor); + } + + void AddHyperlink(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end, const WString& reference, const WString& normalStyleName, const WString& activeStyleName) + { + AddHyperlinkVisitor visitor(runRanges, start, end, reference, normalStyleName, activeStyleName); + run->Accept(&visitor); + } + + void AddStyleName(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end, const WString& styleName) + { + AddStyleNameVisitor visitor(runRanges, start, end, styleName); + run->Accept(&visitor); + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_CLEARUNNECESSARYRUN.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + using namespace document_editor; + +/*********************************************************************** +Clear all runs that have an empty length +***********************************************************************/ + + namespace document_operation_visitors + { + class ClearRunVisitor : public Object, public DocumentRun::IVisitor + { + public: + vint start; + + ClearRunVisitor() + :start(0) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + for (vint i = run->runs.Count() - 1; i >= 0; i--) + { + vint oldStart = start; + run->runs[i]->Accept(this); + if (oldStart == start) + { + run->runs.RemoveAt(i); + } + } + } + + void VisitContent(DocumentContentRun* run) + { + start += run->GetRepresentationText().Length(); + } + + void Visit(DocumentTextRun* run)override + { + VisitContent(run); + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + VisitContent(run); + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + VisitContent(run); + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + +/*********************************************************************** +Ensure DocumentStylePropertiesRun doesn't have a child which is another DocumentStylePropertiesRun +Remove DocumentStylePropertiesRun's property if it set a value but doesn't change the output +Remove DocumentStylePropertiesRun if it is empty or contains no text run +***********************************************************************/ + + namespace document_operation_visitors + { + class CompressStyleRunVisitor : public Object, public DocumentRun::IVisitor + { + public: + DocumentModel* model; + List resolvedStyles; + List> replacedRuns; + + CompressStyleRunVisitor(DocumentModel* _model) + :model(_model) + { + DocumentModel::ResolvedStyle resolvedStyle; + resolvedStyle = model->GetStyle(DocumentModel::DefaultStyleName, resolvedStyle); + resolvedStyles.Add(resolvedStyle); + } + + const DocumentModel::ResolvedStyle& GetCurrentResolvedStyle() + { + return resolvedStyles[resolvedStyles.Count() - 1]; + } + + void VisitContainer(DocumentContainerRun* run) + { + for (vint i = 0; i < run->runs.Count(); i++) + { + Ptr subRun = run->runs[i]; + replacedRuns.Clear(); + subRun->Accept(this); + if (replacedRuns.Count() > 0) + { + run->runs.RemoveAt(i); + for (vint j = 0; j < replacedRuns.Count(); j++) + { + run->runs.Insert(i + j, replacedRuns[j]); + } + i--; + } + } + } + + void Visit(DocumentTextRun* run)override + { + } + + bool OnlyImageOrObject(DocumentContainerRun* run) + { + bool onlyImageOrObject = true; + FOREACH(Ptr, subRun, run->runs) + { + if (!subRun.Cast() && !subRun.Cast()) + { + onlyImageOrObject = false; + break; + } + } + return onlyImageOrObject; + } + + void Visit(DocumentStylePropertiesRun* run)override + { + if (OnlyImageOrObject(run)) + { + CopyFrom(replacedRuns, run->runs); + return; + } + + const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); + DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->style, currentResolvedStyle); + + if (currentResolvedStyle.style.fontFamily == resolvedStyle.style.fontFamily) run->style->face = Nullable(); + if (currentResolvedStyle.style.size == resolvedStyle.style.size) run->style->size = Nullable(); + if (currentResolvedStyle.color == resolvedStyle.color) run->style->color = Nullable(); + if (currentResolvedStyle.backgroundColor == resolvedStyle.backgroundColor) run->style->backgroundColor = Nullable(); + if (currentResolvedStyle.style.bold == resolvedStyle.style.bold) run->style->bold = Nullable(); + if (currentResolvedStyle.style.italic == resolvedStyle.style.italic) run->style->italic = Nullable(); + if (currentResolvedStyle.style.underline == resolvedStyle.style.underline) run->style->underline = Nullable(); + if (currentResolvedStyle.style.strikeline == resolvedStyle.style.strikeline) run->style->strikeline = Nullable(); + if (currentResolvedStyle.style.antialias == resolvedStyle.style.antialias) run->style->antialias = Nullable(); + if (currentResolvedStyle.style.verticalAntialias == resolvedStyle.style.verticalAntialias) run->style->verticalAntialias = Nullable(); + + if (run->style->face) goto CONTINUE_PROCESSING; + if (run->style->size) goto CONTINUE_PROCESSING; + if (run->style->color) goto CONTINUE_PROCESSING; + if (run->style->backgroundColor) goto CONTINUE_PROCESSING; + if (run->style->bold) goto CONTINUE_PROCESSING; + if (run->style->italic) goto CONTINUE_PROCESSING; + if (run->style->underline) goto CONTINUE_PROCESSING; + if (run->style->strikeline) goto CONTINUE_PROCESSING; + if (run->style->antialias) goto CONTINUE_PROCESSING; + if (run->style->verticalAntialias) goto CONTINUE_PROCESSING; + CopyFrom(replacedRuns, run->runs); + return; + + CONTINUE_PROCESSING: + if (From(run->runs).Cast().First(nullptr) != nullptr) + { + FOREACH(Ptr, subRun, run->runs) + { + if (auto styleRun = subRun.Cast()) + { + DocumentModel::MergeStyle(styleRun->style, run->style); + replacedRuns.Add(styleRun); + } + else + { + auto parentRun = CopyRun(run).Cast(); + parentRun->runs.Add(subRun); + replacedRuns.Add(parentRun); + } + } + return; + } + + resolvedStyles.Add(resolvedStyle); + VisitContainer(run); + resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + if (OnlyImageOrObject(run)) + { + CopyFrom(replacedRuns, run->runs); + return; + } + + const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); + DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->styleName, currentResolvedStyle); + resolvedStyles.Add(resolvedStyle); + VisitContainer(run); + resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); + } + + void Visit(DocumentHyperlinkRun* run)override + { + const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); + DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->styleName, currentResolvedStyle); + resolvedStyles.Add(resolvedStyle); + VisitContainer(run); + resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); + } + + void Visit(DocumentImageRun* run)override + { + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + +/*********************************************************************** +Merge sibling runs if they are exactly the same +***********************************************************************/ + + namespace document_operation_visitors + { + class MergeSiblingRunVisitor : public Object, public DocumentRun::IVisitor + { + public: + Ptr nextRun; + Ptr replacedRun; + + void Visit(DocumentTextRun* run)override + { + if (auto sibilingRun = nextRun.Cast()) + { + run->text += sibilingRun->text; + replacedRun = run; + } + } + + void Visit(DocumentStylePropertiesRun* run)override + { + if (auto sibilingRun = nextRun.Cast()) + { + if (run->style->face != sibilingRun->style->face) return; + if (run->style->size != sibilingRun->style->size) return; + if (run->style->color != sibilingRun->style->color) return; + if (run->style->backgroundColor != sibilingRun->style->backgroundColor) return; + if (run->style->bold != sibilingRun->style->bold) return; + if (run->style->italic != sibilingRun->style->italic) return; + if (run->style->underline != sibilingRun->style->underline) return; + if (run->style->strikeline != sibilingRun->style->strikeline) return; + if (run->style->antialias != sibilingRun->style->antialias) return; + if (run->style->verticalAntialias != sibilingRun->style->verticalAntialias) return; + + CopyFrom(run->runs, sibilingRun->runs, true); + replacedRun = run; + } + } + + void Visit(DocumentStyleApplicationRun* run)override + { + if (auto sibilingRun = nextRun.Cast()) + { + if (run->styleName == sibilingRun->styleName) + { + CopyFrom(run->runs, sibilingRun->runs, true); + replacedRun = run; + } + } + } + + void Visit(DocumentHyperlinkRun* run)override + { + if (auto sibilingRun = nextRun.Cast()) + { + if (run->styleName == sibilingRun->styleName && + run->normalStyleName == sibilingRun->normalStyleName && + run->activeStyleName == sibilingRun->activeStyleName && + run->reference == sibilingRun->reference) + { + CopyFrom(run->runs, sibilingRun->runs, true); + replacedRun = run; + } + } + } + + void Visit(DocumentImageRun* run)override + { + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + } + }; + + class MergeSiblingRunRecursivelyVisitor : public Object, public DocumentRun::IVisitor + { + public: + Ptr replacedRun; + Ptr nextRun; + + void VisitContainer(DocumentContainerRun* run) + { + for (vint i = 0; i < run->runs.Count() - 1; i++) + { + auto currentRun = run->runs[i]; + auto nextRun = run->runs[i + 1]; + + MergeSiblingRunVisitor visitor; + visitor.nextRun = nextRun; + currentRun->Accept(&visitor); + + if (visitor.replacedRun) + { + run->runs.RemoveAt(i + 1); + run->runs[i] = visitor.replacedRun; + i--; + } + } + + for (vint i = 0; i < run->runs.Count() - 1; i++) + { + run->runs[i]->Accept(this); + } + } + + void Visit(DocumentTextRun* run)override + { + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + void ClearUnnecessaryRun(DocumentParagraphRun* run, DocumentModel* model) + { + { + ClearRunVisitor visitor; + run->Accept(&visitor); + } + { + CompressStyleRunVisitor visitor(model); + run->Accept(&visitor); + } + { + MergeSiblingRunRecursivelyVisitor visitor; + run->Accept(&visitor); + } + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_CLONERUN.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + using namespace document_editor; + +/*********************************************************************** +Clone the current run without its children +If clonedRun field is assigned then it will be added to the cloned container run +***********************************************************************/ + + namespace document_operation_visitors + { + class CloneRunVisitor : public Object, public DocumentRun::IVisitor + { + public: + Ptr clonedRun; + + CloneRunVisitor(Ptr subRun) + :clonedRun(subRun) + { + } + + void VisitContainer(Ptr cloned) + { + if (clonedRun) + { + cloned->runs.Add(clonedRun); + } + clonedRun = cloned; + } + + void Visit(DocumentTextRun* run)override + { + Ptr cloned = new DocumentTextRun; + cloned->text = run->text; + clonedRun = cloned; + } + + void Visit(DocumentStylePropertiesRun* run)override + { + Ptr cloned = new DocumentStylePropertiesRun; + cloned->style = CopyStyle(run->style); + VisitContainer(cloned); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + Ptr cloned = new DocumentStyleApplicationRun; + cloned->styleName = run->styleName; + + VisitContainer(cloned); + } + + void Visit(DocumentHyperlinkRun* run)override + { + Ptr cloned = new DocumentHyperlinkRun; + cloned->styleName = run->styleName; + cloned->normalStyleName = run->normalStyleName; + cloned->activeStyleName = run->activeStyleName; + cloned->reference = run->reference; + + VisitContainer(cloned); + } + + void Visit(DocumentImageRun* run)override + { + Ptr cloned = new DocumentImageRun; + cloned->size = run->size; + cloned->baseline = run->baseline; + cloned->image = run->image; + cloned->frameIndex = run->frameIndex; + cloned->source = run->source; + clonedRun = cloned; + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + Ptr cloned = new DocumentEmbeddedObjectRun; + cloned->name = run->name; + clonedRun = cloned; + } + + void Visit(DocumentParagraphRun* run)override + { + Ptr cloned = new DocumentParagraphRun; + cloned->alignment = run->alignment; + + VisitContainer(cloned); + } + }; + } + using namespace document_operation_visitors; + +/*********************************************************************** +Clone the current run with its children +***********************************************************************/ + + namespace document_operation_visitors + { + class CloneRunRecursivelyVisitor : public Object, public DocumentRun::IVisitor + { + public: + Ptr clonedRun; + RunRangeMap& runRanges; + vint start; + vint end; + bool deepCopy; + + CloneRunRecursivelyVisitor(RunRangeMap& _runRanges, vint _start, vint _end, bool _deepCopy) + :runRanges(_runRanges) + , start(_start) + , end(_end) + , deepCopy(_deepCopy) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + clonedRun = 0; + RunRange range = runRanges[run]; + if (range.start <= end && start <= range.end) + { + if (start <= range.start && range.end <= end && !deepCopy) + { + clonedRun = run; + } + else + { + Ptr containerRun = CopyRun(run).Cast(); + FOREACH(Ptr, subRun, run->runs) + { + subRun->Accept(this); + if (clonedRun) + { + containerRun->runs.Add(clonedRun); + } + } + clonedRun = containerRun; + } + } + } + + void Visit(DocumentTextRun* run)override + { + clonedRun = 0; + RunRange range = runRanges[run]; + if (range.start textRun = new DocumentTextRun; + vint copyStart = start>range.start ? start : range.start; + vint copyEnd = endtext = run->text.Sub(copyStart - range.start, copyEnd - copyStart); + } + clonedRun = textRun; + } + } + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + clonedRun = 0; + RunRange range = runRanges[run]; + if (range.start CopyStyle(Ptr style) + { + Ptr newStyle = new DocumentStyleProperties; + + newStyle->face = style->face; + newStyle->size = style->size; + newStyle->color = style->color; + newStyle->backgroundColor = style->backgroundColor; + newStyle->bold = style->bold; + newStyle->italic = style->italic; + newStyle->underline = style->underline; + newStyle->strikeline = style->strikeline; + newStyle->antialias = style->antialias; + newStyle->verticalAntialias = style->verticalAntialias; + + return newStyle; + } + + Ptr CopyRun(DocumentRun* run) + { + CloneRunVisitor visitor(0); + run->Accept(&visitor); + return visitor.clonedRun; + } + + Ptr CopyStyledText(List& styleRuns, const WString& text) + { + Ptr textRun = new DocumentTextRun; + textRun->text = text; + + CloneRunVisitor visitor(textRun); + for (vint i = styleRuns.Count() - 1; i >= 0; i--) + { + styleRuns[i]->Accept(&visitor); + } + + return visitor.clonedRun; + } + + Ptr CopyRunRecursively(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end, bool deepCopy) + { + CloneRunRecursivelyVisitor visitor(runRanges, start, end, deepCopy); + run->Accept(&visitor); + return visitor.clonedRun; + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_COLLECTSTYLE.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + +/*********************************************************************** +Search all used style names +***********************************************************************/ + + namespace document_operation_visitors + { + class CollectStyleNameVisitor : public Object, public DocumentRun::IVisitor + { + public: + List& styleNames; + + CollectStyleNameVisitor(List& _styleNames) + :styleNames(_styleNames) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + FOREACH(Ptr, subRun, run->runs) + { + subRun->Accept(this); + } + } + + void Visit(DocumentTextRun* run)override + { + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + if (!styleNames.Contains(run->styleName)) + { + styleNames.Add(run->styleName); + } + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + if (!styleNames.Contains(run->normalStyleName)) + { + styleNames.Add(run->normalStyleName); + } + if (!styleNames.Contains(run->activeStyleName)) + { + styleNames.Add(run->activeStyleName); + } + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + void CollectStyleName(DocumentParagraphRun* run, List& styleNames) + { + CollectStyleNameVisitor visitor(styleNames); + run->Accept(&visitor); + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_CUTRUN.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + using namespace document_editor; + +/*********************************************************************** +Cut all runs into pieces so that a run either completely outside or inside the specified range +If a run decides that itself should be cut, then leftRun and rightRun contains new run that will be inserted before and after it +***********************************************************************/ + + namespace document_operation_visitors + { + class CutRunVisitor : public Object, public DocumentRun::IVisitor + { + public: + RunRangeMap& runRanges; + vint position; + Ptr leftRun; + Ptr rightRun; + + CutRunVisitor(RunRangeMap& _runRanges, vint _position) + :runRanges(_runRanges) + , position(_position) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + vint leftCount = 0; + Ptr selectedRun; + + FOREACH(Ptr, subRun, run->runs) + { + RunRange range = runRanges[subRun.Obj()]; + if (range.startAccept(this); + if (leftRun && rightRun) + { + run->runs.RemoveAt(leftCount - 1); + run->runs.Insert(leftCount - 1, leftRun); + run->runs.Insert(leftCount, rightRun); + } + } + + Ptr leftContainer = CopyRun(run).Cast(); + Ptr rightContainer = CopyRun(run).Cast(); + for (vint i = 0; iruns.Count(); i++) + { + (iruns.Add(run->runs[i]); + } + leftRun = leftContainer; + rightRun = rightContainer; + } + + void Visit(DocumentTextRun* run)override + { + RunRange range = runRanges[run]; + + Ptr leftText = new DocumentTextRun; + leftText->text = run->text.Sub(0, position - range.start); + + Ptr rightText = new DocumentTextRun; + rightText->text = run->text.Sub(position - range.start, range.end - position); + + leftRun = leftText; + rightRun = rightText; + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + leftRun = 0; + rightRun = 0; + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + leftRun = 0; + rightRun = 0; + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + void CutRun(DocumentParagraphRun* run, RunRangeMap& runRanges, vint position, Ptr& leftRun, Ptr& rightRun) + { + CutRunVisitor visitor(runRanges, position); + run->Accept(&visitor); + leftRun = visitor.leftRun; + rightRun = visitor.rightRun; + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_GETRUNRANGE.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + +/*********************************************************************** +Calculate range informations for each run object +***********************************************************************/ + + namespace document_operation_visitors + { + class GetRunRangeVisitor : public Object, public DocumentRun::IVisitor + { + public: + RunRangeMap& runRanges; + vint start; + + GetRunRangeVisitor(RunRangeMap& _runRanges) + :runRanges(_runRanges) + , start(0) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + RunRange range; + range.start = start; + FOREACH(Ptr, subRun, run->runs) + { + subRun->Accept(this); + } + range.end = start; + runRanges.Add(run, range); + } + + void VisitContent(DocumentContentRun* run) + { + RunRange range; + range.start = start; + start += run->GetRepresentationText().Length(); + range.end = start; + runRanges.Add(run, range); + } + + void Visit(DocumentTextRun* run)override + { + VisitContent(run); + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + VisitContent(run); + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + VisitContent(run); + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + void GetRunRange(DocumentParagraphRun* run, RunRangeMap& runRanges) + { + GetRunRangeVisitor visitor(runRanges); + run->Accept(&visitor); + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_LOCALEHYPERLINK.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + +/*********************************************************************** +Get the hyperlink run that contains the specified position +***********************************************************************/ + + namespace document_operation_visitors + { + class LocateHyperlinkVisitor : public Object, public DocumentRun::IVisitor + { + public: + Ptr package; + RunRangeMap& runRanges; + vint start; + vint end; + + LocateHyperlinkVisitor(RunRangeMap& _runRanges, Ptr _package, vint _start, vint _end) + :runRanges(_runRanges) + , package(_package) + , start(_start) + , end(_end) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + Ptr selectedRun; + FOREACH(Ptr, subRun, run->runs) + { + RunRange range = runRanges[subRun.Obj()]; + if (range.start <= start && end <= range.end) + { + subRun->Accept(this); + break; + } + } + } + + void Visit(DocumentTextRun* run)override + { + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + package->hyperlinks.Add(run); + } + + void Visit(DocumentImageRun* run)override + { + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + Ptr LocateHyperlink(DocumentParagraphRun* run, RunRangeMap& runRanges, vint row, vint start, vint end) + { + auto package = MakePtr(); + package->row = row; + { + LocateHyperlinkVisitor visitor(runRanges, package, start, end); + run->Accept(&visitor); + } + + Ptr startRun, endRun; + FOREACH(Ptr, run, package->hyperlinks) + { + auto range = runRanges[run.Obj()]; + if (package->start == -1 || range.start < package->start) + { + package->start = range.start; + startRun = run; + } + if (package->end == -1 || range.end > package->end) + { + package->end = range.end; + endRun = run; + } + } + + while (startRun) + { + vint pos = runRanges[startRun.Obj()].start; + if (pos == 0) break; + + auto newPackage = MakePtr(); + LocateHyperlinkVisitor visitor(runRanges, newPackage, pos - 1, pos); + run->Accept(&visitor); + if (newPackage->hyperlinks.Count() == 0) break; + + auto newRun = newPackage->hyperlinks[0]; + if (startRun->reference != newRun->reference) break; + + auto range = runRanges[newRun.Obj()]; + package->hyperlinks.Add(newRun); + package->start = range.start; + startRun = newRun; + } + + vint length = runRanges[run].end; + while (endRun) + { + vint pos = runRanges[endRun.Obj()].end; + if (pos == length) break; + + auto newPackage = MakePtr(); + LocateHyperlinkVisitor visitor(runRanges, newPackage, pos, pos + 1); + run->Accept(&visitor); + if (newPackage->hyperlinks.Count() == 0) break; + + auto newRun = newPackage->hyperlinks[0]; + if (endRun->reference != newRun->reference) break; + + auto range = runRanges[newRun.Obj()]; + package->hyperlinks.Add(newRun); + package->end = range.end; + endRun = newRun; + } + + return package; + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_LOCALESTYLE.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + +/*********************************************************************** +Get all container runs that contain the specified position from top to bottom +***********************************************************************/ + + namespace document_operation_visitors + { + class LocateStyleVisitor : public Object, public DocumentRun::IVisitor + { + public: + List& locatedRuns; + RunRangeMap& runRanges; + vint position; + bool frontSide; + + LocateStyleVisitor(List& _locatedRuns, RunRangeMap& _runRanges, vint _position, bool _frontSide) + :locatedRuns(_locatedRuns) + , runRanges(_runRanges) + , position(_position) + , frontSide(_frontSide) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + locatedRuns.Add(run); + Ptr selectedRun; + FOREACH(Ptr, subRun, run->runs) + { + RunRange range = runRanges[subRun.Obj()]; + if (position == range.start) + { + if (!frontSide) + { + selectedRun = subRun; + break; + } + } + else if (position == range.end) + { + if (frontSide) + { + selectedRun = subRun; + break; + } + } + else if (range.startAccept(this); + } + } + + void Visit(DocumentTextRun* run)override + { + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + void LocateStyle(DocumentParagraphRun* run, RunRangeMap& runRanges, vint position, bool frontSide, List& locatedRuns) + { + LocateStyleVisitor visitor(locatedRuns, runRanges, position, frontSide); + run->Accept(&visitor); + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_REMOVECONTAINER.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + +/*********************************************************************** +Remove some containers that intersect with the specified range +If a run decides that itself should be removed, then replacedRuns contains all runs to replace itself +RemoveHyperlinkVisitor : Remove all hyperlinks that intersect with the specified range +RemoveStyleNameVisitor : Remove all style names that intersect with the specified range +ClearStyleVisitor : Remove all styles that intersect with the specified range +***********************************************************************/ + + namespace document_operation_visitors + { + class RemoveContainerVisitor : public Object, public DocumentRun::IVisitor + { + public: + RunRangeMap& runRanges; + vint start; + vint end; + List> replacedRuns; + + RemoveContainerVisitor(RunRangeMap& _runRanges, vint _start, vint _end) + :runRanges(_runRanges) + , start(_start) + , end(_end) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + for (vint i = run->runs.Count() - 1; i >= 0; i--) + { + Ptr subRun = run->runs[i]; + RunRange range = runRanges[subRun.Obj()]; + if (range.startAccept(this); + if (replacedRuns.Count() != 1 || replacedRuns[0] != subRun) + { + run->runs.RemoveAt(i); + for (vint j = 0; jruns.Insert(i + j, replacedRuns[j]); + } + i += replacedRuns.Count(); + } + } + } + replacedRuns.Clear(); + replacedRuns.Add(run); + } + + void VisitContent(DocumentContentRun* run) + { + replacedRuns.Add(run); + } + + void RemoveContainer(DocumentContainerRun* run) + { + CopyFrom(replacedRuns, run->runs); + } + + void Visit(DocumentTextRun* run)override + { + VisitContent(run); + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + VisitContent(run); + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + VisitContent(run); + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + + class RemoveHyperlinkVisitor : public RemoveContainerVisitor + { + public: + RemoveHyperlinkVisitor(RunRangeMap& _runRanges, vint _start, vint _end) + :RemoveContainerVisitor(_runRanges, _start, _end) + { + } + + void Visit(DocumentHyperlinkRun* run)override + { + RemoveContainer(run); + } + }; + + class RemoveStyleNameVisitor : public RemoveContainerVisitor + { + public: + RemoveStyleNameVisitor(RunRangeMap& _runRanges, vint _start, vint _end) + :RemoveContainerVisitor(_runRanges, _start, _end) + { + } + + void Visit(DocumentStyleApplicationRun* run)override + { + RemoveContainer(run); + } + }; + + class ClearStyleVisitor : public RemoveContainerVisitor + { + public: + ClearStyleVisitor(RunRangeMap& _runRanges, vint _start, vint _end) + :RemoveContainerVisitor(_runRanges, _start, _end) + { + } + + void Visit(DocumentStylePropertiesRun* run)override + { + RemoveContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + RemoveContainer(run); + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + void RemoveHyperlink(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end) + { + RemoveHyperlinkVisitor visitor(runRanges, start, end); + run->Accept(&visitor); + } + + void RemoveStyleName(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end) + { + RemoveStyleNameVisitor visitor(runRanges, start, end); + run->Accept(&visitor); + } + + void ClearStyle(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end) + { + ClearStyleVisitor visitor(runRanges, start, end); + run->Accept(&visitor); + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_REMOVERUN.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + +/*********************************************************************** +Remove text run contents with the specified range, or other content runs that intersect with the range +If a run decides that itself should be removed, then replacedRuns contains all runs to replace itself +***********************************************************************/ + + namespace document_operation_visitors + { + class RemoveRunVisitor : public Object, public DocumentRun::IVisitor + { + public: + RunRangeMap& runRanges; + vint start; + vint end; + List> replacedRuns; + + RemoveRunVisitor(RunRangeMap& _runRanges, vint _start, vint _end) + :runRanges(_runRanges) + , start(_start) + , end(_end) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + if (start == end) return; + for (vint i = run->runs.Count() - 1; i >= 0; i--) + { + Ptr subRun = run->runs[i]; + RunRange range = runRanges[subRun.Obj()]; + + vint maxStart = range.start > start ? range.start : start; + vint minEnd = range.end < end ? range.end : end; + if (maxStart < minEnd) + { + subRun->Accept(this); + if (replacedRuns.Count() == 0 || subRun != replacedRuns[0]) + { + run->runs.RemoveAt(i); + for (vint j = 0; jruns.Insert(i + j, replacedRuns[j]); + } + } + } + } + replacedRuns.Clear(); + replacedRuns.Add(run); + } + + void Visit(DocumentTextRun* run)override + { + replacedRuns.Clear(); + RunRange range = runRanges[run]; + + if (start <= range.start) + { + if (endtext = run->text.Sub(end - range.start, range.end - end); + replacedRuns.Add(run); + } + } + else + { + if (endtext = run->text.Sub(0, start - range.start); + secondRun->text = run->text.Sub(end - range.start, range.end - end); + + replacedRuns.Add(firstRun); + replacedRuns.Add(secondRun); + } + else + { + run->text = run->text.Sub(0, start - range.start); + replacedRuns.Add(run); + } + } + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + replacedRuns.Clear(); + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + replacedRuns.Clear(); + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + void RemoveRun(DocumentParagraphRun* run, RunRangeMap& runRanges, vint start, vint end) + { + RemoveRunVisitor visitor(runRanges, start, end); + run->Accept(&visitor); + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_REPLACESTYLENAME.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + +/*********************************************************************** +Replace a style name with another one +***********************************************************************/ + + namespace document_operation_visitors + { + class ReplaceStyleNameVisitor : public Object, public DocumentRun::IVisitor + { + public: + WString oldStyleName; + WString newStyleName; + + ReplaceStyleNameVisitor(const WString& _oldStyleName, const WString& _newStyleName) + :oldStyleName(_oldStyleName) + , newStyleName(_newStyleName) + { + } + + void VisitContainer(DocumentContainerRun* run) + { + FOREACH(Ptr, subRun, run->runs) + { + subRun->Accept(this); + } + } + + void Visit(DocumentTextRun* run)override + { + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + if (run->styleName == oldStyleName) run->styleName = newStyleName; + VisitContainer(run); + } + + void Visit(DocumentHyperlinkRun* run)override + { + if (run->styleName == oldStyleName) run->styleName = newStyleName; + if (run->normalStyleName == oldStyleName) run->normalStyleName = newStyleName; + if (run->activeStyleName == oldStyleName) run->activeStyleName = newStyleName; + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + void ReplaceStyleName(DocumentParagraphRun* run, const WString& oldStyleName, const WString& newStyleName) + { + ReplaceStyleNameVisitor visitor(oldStyleName, newStyleName); + run->Accept(&visitor); + } + } + } +} + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENTEDITOR_SUMMERIZESTYLE.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + + namespace document_operation_visitors + { + +/*********************************************************************** +Calculate if all text in the specified range has some common styles +***********************************************************************/ + + class SummarizeStyleVisitor : public Object, public DocumentRun::IVisitor + { + public: + RunRangeMap& runRanges; + DocumentModel* model; + vint start; + vint end; + Ptr style; + List resolvedStyles; + + SummarizeStyleVisitor(RunRangeMap& _runRanges, DocumentModel* _model, vint _start, vint _end) + :runRanges(_runRanges) + , model(_model) + , start(_start) + , end(_end) + { + DocumentModel::ResolvedStyle resolvedStyle; + resolvedStyle = model->GetStyle(DocumentModel::DefaultStyleName, resolvedStyle); + resolvedStyles.Add(resolvedStyle); + } + + const DocumentModel::ResolvedStyle& GetCurrentResolvedStyle() + { + return resolvedStyles[resolvedStyles.Count() - 1]; + } + + // --------------------------------------------------------- + + template + void SetStyleItem(Nullable DocumentStyleProperties::* dstField, T FontProperties::* srcField) + { + const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); + if (style.Obj()->*dstField && (style.Obj()->*dstField).Value() != src.style.*srcField) + { + style.Obj()->*dstField = Nullable(); + } + } + + template + void SetStyleItem(Nullable DocumentStyleProperties::* dstField, T DocumentModel::ResolvedStyle::* srcField) + { + const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); + if (style.Obj()->*dstField && (style.Obj()->*dstField).Value() != src.*srcField) + { + style.Obj()->*dstField = Nullable(); + } + } + + void SetStyleItem(Nullable DocumentStyleProperties::* dstField, vint FontProperties::* srcField) + { + const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); + if (style.Obj()->*dstField) + { + auto dfs = (style.Obj()->*dstField).Value(); + if (dfs.relative || dfs.size != src.style.*srcField) + { + style.Obj()->*dstField = Nullable(); + } + } + } + + // --------------------------------------------------------- + + template + void OverrideStyleItem(Nullable DocumentStyleProperties::* dstField, T FontProperties::* srcField) + { + const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); + style.Obj()->*dstField = src.style.*srcField; + } + + template + void OverrideStyleItem(Nullable DocumentStyleProperties::* dstField, T DocumentModel::ResolvedStyle::* srcField) + { + const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); + style.Obj()->*dstField = src.*srcField; + } + + void OverrideStyleItem(Nullable DocumentStyleProperties::* dstField, vint FontProperties::* srcField) + { + const DocumentModel::ResolvedStyle& src = GetCurrentResolvedStyle(); + style.Obj()->*dstField = DocumentFontSize((double)(src.style.*srcField), false); + } + + // --------------------------------------------------------- + + void VisitContainer(DocumentContainerRun* run) + { + for (vint i = run->runs.Count() - 1; i >= 0; i--) + { + Ptr subRun = run->runs[i]; + RunRange range = runRanges[subRun.Obj()]; + if (range.startAccept(this); + } + } + } + + void Visit(DocumentTextRun* run)override + { + const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); + if (style) + { + SetStyleItem(&DocumentStyleProperties::face, &FontProperties::fontFamily); + SetStyleItem(&DocumentStyleProperties::size, &FontProperties::size); + SetStyleItem(&DocumentStyleProperties::color, &DocumentModel::ResolvedStyle::color); + SetStyleItem(&DocumentStyleProperties::backgroundColor, &DocumentModel::ResolvedStyle::backgroundColor); + SetStyleItem(&DocumentStyleProperties::bold, &FontProperties::bold); + SetStyleItem(&DocumentStyleProperties::italic, &FontProperties::italic); + SetStyleItem(&DocumentStyleProperties::underline, &FontProperties::underline); + SetStyleItem(&DocumentStyleProperties::strikeline, &FontProperties::strikeline); + SetStyleItem(&DocumentStyleProperties::antialias, &FontProperties::antialias); + SetStyleItem(&DocumentStyleProperties::verticalAntialias, &FontProperties::verticalAntialias); + } + else + { + style = new DocumentStyleProperties; + OverrideStyleItem(&DocumentStyleProperties::face, &FontProperties::fontFamily); + OverrideStyleItem(&DocumentStyleProperties::size, &FontProperties::size); + OverrideStyleItem(&DocumentStyleProperties::color, &DocumentModel::ResolvedStyle::color); + OverrideStyleItem(&DocumentStyleProperties::backgroundColor, &DocumentModel::ResolvedStyle::backgroundColor); + OverrideStyleItem(&DocumentStyleProperties::bold, &FontProperties::bold); + OverrideStyleItem(&DocumentStyleProperties::italic, &FontProperties::italic); + OverrideStyleItem(&DocumentStyleProperties::underline, &FontProperties::underline); + OverrideStyleItem(&DocumentStyleProperties::strikeline, &FontProperties::strikeline); + OverrideStyleItem(&DocumentStyleProperties::antialias, &FontProperties::antialias); + OverrideStyleItem(&DocumentStyleProperties::verticalAntialias, &FontProperties::verticalAntialias); + } + } + + void Visit(DocumentStylePropertiesRun* run)override + { + const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); + DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->style, currentResolvedStyle); + resolvedStyles.Add(resolvedStyle); + VisitContainer(run); + resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); + DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->styleName, currentResolvedStyle); + resolvedStyles.Add(resolvedStyle); + VisitContainer(run); + resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); + } + + void Visit(DocumentHyperlinkRun* run)override + { + const DocumentModel::ResolvedStyle& currentResolvedStyle = GetCurrentResolvedStyle(); + DocumentModel::ResolvedStyle resolvedStyle = model->GetStyle(run->styleName, currentResolvedStyle); + resolvedStyles.Add(resolvedStyle); + VisitContainer(run); + resolvedStyles.RemoveAt(resolvedStyles.Count() - 1); + } + + void Visit(DocumentImageRun* run)override + { + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + +/*********************************************************************** +Calculate if all text in the specified range has a common style name +***********************************************************************/ + + class SummarizeStyleNameVisitor : public Object, public DocumentRun::IVisitor + { + public: + RunRangeMap& runRanges; + DocumentModel* model; + vint start; + vint end; + Nullable currentStyleName; + Nullable styleName; + bool assignedStyleName = false; + + SummarizeStyleNameVisitor(RunRangeMap& _runRanges, DocumentModel* _model, vint _start, vint _end) + :runRanges(_runRanges) + , model(_model) + , start(_start) + , end(_end) + { + } + + void VisitContentRun(DocumentContentRun* run) + { + if (!assignedStyleName) + { + styleName = currentStyleName; + assignedStyleName = true; + } + else if (styleName && (!currentStyleName || styleName.Value() != currentStyleName.Value())) + { + styleName = Nullable(); + } + } + + void VisitContainer(DocumentContainerRun* run) + { + for (vint i = run->runs.Count() - 1; i >= 0; i--) + { + Ptr subRun = run->runs[i]; + RunRange range = runRanges[subRun.Obj()]; + if (range.startAccept(this); + } + } + } + + void Visit(DocumentTextRun* run)override + { + VisitContentRun(run); + } + + void Visit(DocumentStylePropertiesRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentStyleApplicationRun* run)override + { + auto oldStyleName = currentStyleName; + currentStyleName = run->styleName; + VisitContainer(run); + currentStyleName = oldStyleName; + } + + void Visit(DocumentHyperlinkRun* run)override + { + VisitContainer(run); + } + + void Visit(DocumentImageRun* run)override + { + VisitContentRun(run); + } + + void Visit(DocumentEmbeddedObjectRun* run)override + { + VisitContentRun(run); + } + + void Visit(DocumentParagraphRun* run)override + { + VisitContainer(run); + } + }; + } + using namespace document_operation_visitors; + + namespace document_editor + { + Ptr SummarizeStyle(DocumentParagraphRun* run, RunRangeMap& runRanges, DocumentModel* model, vint start, vint end) + { + SummarizeStyleVisitor visitor(runRanges, model, start, end); + run->Accept(&visitor); + return visitor.style; + } + + Nullable SummarizeStyleName(DocumentParagraphRun* run, RunRangeMap& runRanges, DocumentModel* model, vint start, vint end) + { + SummarizeStyleNameVisitor visitor(runRanges, model, start, end); + run->Accept(&visitor); + return visitor.styleName; + } + + template + void AggregateStyleItem(Ptr& dst, Ptr src, Nullable DocumentStyleProperties::* field) + { + if (dst.Obj()->*field && (!(src.Obj()->*field) || (dst.Obj()->*field).Value() != (src.Obj()->*field).Value())) + { + dst.Obj()->*field = Nullable(); + } + } + + void AggregateStyle(Ptr& dst, Ptr src) + { + AggregateStyleItem(dst, src, &DocumentStyleProperties::face); + AggregateStyleItem(dst, src, &DocumentStyleProperties::size); + AggregateStyleItem(dst, src, &DocumentStyleProperties::color); + AggregateStyleItem(dst, src, &DocumentStyleProperties::backgroundColor); + AggregateStyleItem(dst, src, &DocumentStyleProperties::bold); + AggregateStyleItem(dst, src, &DocumentStyleProperties::italic); + AggregateStyleItem(dst, src, &DocumentStyleProperties::underline); + AggregateStyleItem(dst, src, &DocumentStyleProperties::strikeline); + AggregateStyleItem(dst, src, &DocumentStyleProperties::antialias); + AggregateStyleItem(dst, src, &DocumentStyleProperties::verticalAntialias); + } + } + } +} + + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENT_EDIT.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + using namespace document_editor; + +/*********************************************************************** +DocumentModel::EditRangeOperations +***********************************************************************/ + + bool DocumentModel::CheckEditRange(TextPos begin, TextPos end, RunRangeMap& relatedRanges) + { + // check caret range + if(begin>end) return false; + if(begin.row<0 || begin.row>=paragraphs.Count()) return false; + if(end.row<0 || end.row>=paragraphs.Count()) return false; + + // determine run ranges + GetRunRange(paragraphs[begin.row].Obj(), relatedRanges); + if(begin.row!=end.row) + { + GetRunRange(paragraphs[end.row].Obj(), relatedRanges); + } + + // check caret range + RunRange beginRange=relatedRanges[paragraphs[begin.row].Obj()]; + RunRange endRange=relatedRanges[paragraphs[end.row].Obj()]; + if(begin.column<0 || begin.column>beginRange.end) return false; + if(end.column<0 || end.column>endRange.end) return false; + + return true; + } + + Ptr DocumentModel::CopyDocument(TextPos begin, TextPos end, bool deepCopy) + { + // check caret range + RunRangeMap runRanges; + if(!CheckEditRange(begin, end, runRanges)) return nullptr; + + // get ranges + for(vint i=begin.row+1;i newDocument=new DocumentModel; + + // copy paragraphs + if(begin.row==end.row) + { + newDocument->paragraphs.Add(CopyRunRecursively(paragraphs[begin.row].Obj(), runRanges, begin.column, end.column, deepCopy).Cast()); + } + else + { + for(vint i=begin.row;i<=end.row;i++) + { + Ptr paragraph=paragraphs[i]; + RunRange range=runRanges[paragraph.Obj()]; + if(i==begin.row) + { + newDocument->paragraphs.Add(CopyRunRecursively(paragraph.Obj(), runRanges, begin.column, range.end, deepCopy).Cast()); + } + else if(i==end.row) + { + newDocument->paragraphs.Add(CopyRunRecursively(paragraph.Obj(), runRanges, range.start, end.column, deepCopy).Cast()); + } + else if(deepCopy) + { + newDocument->paragraphs.Add(CopyRunRecursively(paragraph.Obj(), runRanges, range.start, range.end, deepCopy).Cast()); + } + else + { + newDocument->paragraphs.Add(paragraph); + } + } + } + + // copy styles + List styleNames; + FOREACH(Ptr, paragraph, newDocument->paragraphs) + { + CollectStyleName(paragraph.Obj(), styleNames); + } + + for(vint i=0;istyles.Keys().Contains(styleName)) + { + Ptr style=styles[styleName]; + if(deepCopy) + { + Ptr newStyle=new DocumentStyle; + newStyle->parentStyleName=style->parentStyleName; + newStyle->styles=CopyStyle(style->styles); + newStyle->resolvedStyles=CopyStyle(style->resolvedStyles); + newDocument->styles.Add(styleName, newStyle); + } + else + { + newDocument->styles.Add(styleName, style); + } + + if(!styleNames.Contains(style->parentStyleName)) + { + styleNames.Add(style->parentStyleName); + } + } + } + + return newDocument; + } + + Ptr DocumentModel::CopyDocument() + { + // determine run ranges + RunRangeMap runRanges; + vint lastParagraphIndex = paragraphs.Count() - 1; + GetRunRange(paragraphs[lastParagraphIndex].Obj(), runRanges); + + TextPos begin(0, 0); + TextPos end(lastParagraphIndex, runRanges[paragraphs[lastParagraphIndex].Obj()].end); + return CopyDocument(begin, end, true); + } + + bool DocumentModel::CutParagraph(TextPos position) + { + if(position.row<0 || position.row>=paragraphs.Count()) return false; + + Ptr paragraph=paragraphs[position.row]; + RunRangeMap runRanges; + Ptr leftRun, rightRun; + + GetRunRange(paragraph.Obj(), runRanges); + CutRun(paragraph.Obj(), runRanges, position.column, leftRun, rightRun); + + CopyFrom(paragraph->runs, leftRun.Cast()->runs); + CopyFrom(paragraph->runs, rightRun.Cast()->runs, true); + + return true; + } + + bool DocumentModel::CutEditRange(TextPos begin, TextPos end) + { + // check caret range + if(begin>end) return false; + if(begin.row<0 || begin.row>=paragraphs.Count()) return false; + if(end.row<0 || end.row>=paragraphs.Count()) return false; + + // cut paragraphs + CutParagraph(begin); + if(begin!=end) + { + CutParagraph(end); + } + return true; + } + + bool DocumentModel::EditContainer(TextPos begin, TextPos end, const Func& editor) + { + if(begin==end) return false; + + // cut paragraphs + if(!CutEditRange(begin, end)) return false; + + // check caret range + RunRangeMap runRanges; + if(!CheckEditRange(begin, end, runRanges)) return false; + + // edit container + if(begin.row==end.row) + { + editor(paragraphs[begin.row].Obj(), runRanges, begin.column, end.column); + } + else + { + for(vint i=begin.row;i<=end.row;i++) + { + Ptr paragraph=paragraphs[i]; + if(begin.row replaceToModel, bool copy) + { + // check caret range + RunRangeMap runRanges; + if(!CheckEditRange(begin, end, runRanges)) return -1; + + auto model = replaceToModel; + if (copy) + { + model = replaceToModel->CopyDocument(); + } + + // calculate new names for the model's styles to prevent conflicting + List oldNames, newNames; + CopyFrom(oldNames, model->styles.Keys()); + CopyFrom(newNames, model->styles.Keys()); + for(vint i=0;istyles.Keys().Contains(newName)) + { + newNames[i]=newName; + break; + } + } + } + } + + // rename model's styles + typedef Pair NamePair; + FOREACH(NamePair, name, From(oldNames).Pairwise(newNames)) + { + model->RenameStyle(name.key, name.value); + } + FOREACH(WString, name, newNames) + { + if((name.Length()==0 || name[0]!=L'#') && !styles.Keys().Contains(name)) + { + styles.Add(name, model->styles[name]); + } + } + + // edit runs + Array> runs; + CopyFrom(runs, model->paragraphs); + return EditRunNoCopy(begin, end, runs); + } + + vint DocumentModel::EditRunNoCopy(TextPos begin, TextPos end, const collections::Array>& runs) + { + // check caret range + RunRangeMap runRanges; + if(!CheckEditRange(begin, end, runRanges)) return -1; + + // remove unnecessary paragraphs + if(begin.row!=end.row) + { + for(vint i=end.row-1;i>begin.row;i--) + { + paragraphs.RemoveAt(i); + } + end.row=begin.row+1; + } + + // remove unnecessary runs and ensure begin.row!=end.row + if(begin.row==end.row) + { + RemoveRun(paragraphs[begin.row].Obj(), runRanges, begin.column, end.column); + + Ptr leftRun, rightRun; + runRanges.Clear(); + GetRunRange(paragraphs[begin.row].Obj(), runRanges); + CutRun(paragraphs[begin.row].Obj(), runRanges, begin.column, leftRun, rightRun); + + paragraphs.RemoveAt(begin.row); + paragraphs.Insert(begin.row, leftRun.Cast()); + paragraphs.Insert(begin.row+1, rightRun.Cast()); + end.row=begin.row+1; + } + else + { + RemoveRun(paragraphs[begin.row].Obj(), runRanges, begin.column, runRanges[paragraphs[begin.row].Obj()].end); + RemoveRun(paragraphs[end.row].Obj(), runRanges, 0, end.column); + } + + // insert new paragraphs + Ptr beginParagraph=paragraphs[begin.row]; + Ptr endParagraph=paragraphs[end.row]; + if(runs.Count()==0) + { + CopyFrom(beginParagraph->runs, endParagraph->runs, true); + paragraphs.RemoveAt(end.row); + } + else if(runs.Count()==1) + { + CopyFrom(beginParagraph->runs, runs[0]->runs, true); + CopyFrom(beginParagraph->runs, endParagraph->runs, true); + paragraphs.RemoveAt(end.row); + } + else + { + Ptr newBeginRuns=runs[0]; + CopyFrom(beginParagraph->runs, newBeginRuns->runs, true); + + Ptr newEndRuns=runs[runs.Count()-1]; + if (newEndRuns->alignment) + { + endParagraph->alignment = newEndRuns->alignment; + } + for(vint i=0;iruns.Count();i++) + { + endParagraph->runs.Insert(i, newEndRuns->runs[i]); + } + + for(vint i=1;i& text) + { + // check caret range + RunRangeMap runRanges; + if(!CheckEditRange(begin, end, runRanges)) return -1; + + // calcuate the position to get the text style + TextPos stylePosition; + if(frontSide) + { + stylePosition=begin; + if(stylePosition.column==0) + { + frontSide=false; + } + } + else + { + stylePosition=end; + if(stylePosition.column==runRanges[paragraphs[end.row].Obj()].end) + { + frontSide=true; + } + } + + // copy runs that contains the target style for new text + List styleRuns; + LocateStyle(paragraphs[stylePosition.row].Obj(), runRanges, stylePosition.column, frontSide, styleRuns); + + // create paragraphs + Array> runs(text.Count()); + for(vint i=0;i paragraph=CopyStyledText(styleRuns, text[i]); + runs[i]=paragraph.Cast(); + } + + // replace the paragraphs + return EditRunNoCopy(begin, end, runs); + } + +/*********************************************************************** +DocumentModel::EditStyle +***********************************************************************/ + + bool DocumentModel::EditStyle(TextPos begin, TextPos end, Ptr style) + { + return EditContainer(begin, end, [=](DocumentParagraphRun* paragraph, RunRangeMap& runRanges, vint start, vint end) + { + AddStyle(paragraph, runRanges, start, end, style); + }); + } + +/*********************************************************************** +DocumentModel::EditImage +***********************************************************************/ + + Ptr DocumentModel::EditImage(TextPos begin, TextPos end, Ptr image) + { + Ptr imageRun=new DocumentImageRun; + imageRun->size=image->GetImage()->GetFrame(image->GetFrameIndex())->GetSize(); + imageRun->baseline=imageRun->size.y; + imageRun->image=image->GetImage(); + imageRun->frameIndex=image->GetFrameIndex(); + + Ptr paragraph=new DocumentParagraphRun; + paragraph->runs.Add(imageRun); + + Array> runs(1); + runs[0]=paragraph; + if(EditRunNoCopy(begin, end, runs)) + { + return imageRun; + } + else + { + return 0; + } + } + +/*********************************************************************** +DocumentModel::EditHyperlink +***********************************************************************/ + + bool DocumentModel::EditHyperlink(vint paragraphIndex, vint begin, vint end, const WString& reference, const WString& normalStyleName, const WString& activeStyleName) + { + auto package = GetHyperlink(paragraphIndex, begin, end); + if (package->hyperlinks.Count() > 0) + { + FOREACH(Ptr, run, package->hyperlinks) + { + run->reference = reference; + run->normalStyleName = normalStyleName; + run->activeStyleName = activeStyleName; + run->styleName = normalStyleName; + } + return true; + } + else if (RemoveHyperlink(paragraphIndex, begin, end)) + { + CutEditRange(TextPos(paragraphIndex, begin), TextPos(paragraphIndex, end)); + + RunRangeMap runRanges; + Ptr paragraph = paragraphs[paragraphIndex]; + GetRunRange(paragraph.Obj(), runRanges); + AddHyperlink(paragraph.Obj(), runRanges, begin, end, reference, normalStyleName, activeStyleName); + + ClearUnnecessaryRun(paragraph.Obj(), this); + return true; + } + return false; + } + + bool DocumentModel::RemoveHyperlink(vint paragraphIndex, vint begin, vint end) + { + RunRangeMap runRanges; + if (!CheckEditRange(TextPos(paragraphIndex, begin), TextPos(paragraphIndex, end), runRanges)) return 0; + + auto paragraph = paragraphs[paragraphIndex]; + auto package = LocateHyperlink(paragraph.Obj(), runRanges, paragraphIndex, begin, end); + document_editor::RemoveHyperlink(paragraph.Obj(), runRanges, package->start, package->end); + ClearUnnecessaryRun(paragraph.Obj(), this); + return true; + } + + Ptr DocumentModel::GetHyperlink(vint paragraphIndex, vint begin, vint end) + { + RunRangeMap runRanges; + if (!CheckEditRange(TextPos(paragraphIndex, begin), TextPos(paragraphIndex, end), runRanges)) return 0; + + auto paragraph = paragraphs[paragraphIndex]; + return LocateHyperlink(paragraph.Obj(), runRanges, paragraphIndex, begin, end); + } + +/*********************************************************************** +DocumentModel::EditStyleName +***********************************************************************/ + + bool DocumentModel::EditStyleName(TextPos begin, TextPos end, const WString& styleName) + { + return EditContainer(begin, end, [=](DocumentParagraphRun* paragraph, RunRangeMap& runRanges, vint start, vint end) + { + AddStyleName(paragraph, runRanges, start, end, styleName); + }); + } + + bool DocumentModel::RemoveStyleName(TextPos begin, TextPos end) + { + return EditContainer(begin, end, [=](DocumentParagraphRun* paragraph, RunRangeMap& runRanges, vint start, vint end) + { + document_editor::RemoveStyleName(paragraph, runRanges, start, end); + }); + } + + bool DocumentModel::RenameStyle(const WString& oldStyleName, const WString& newStyleName) + { + vint index=styles.Keys().IndexOf(oldStyleName); + if(index==-1) return false; + if(styles.Keys().Contains(newStyleName)) return false; + + Ptr style=styles.Values()[index]; + styles.Remove(oldStyleName); + styles.Add(newStyleName, style); + + FOREACH(Ptr, subStyle, styles.Values()) + { + if(subStyle->parentStyleName==oldStyleName) + { + subStyle->parentStyleName=newStyleName; + } + } + + FOREACH(Ptr, paragraph, paragraphs) + { + ReplaceStyleName(paragraph.Obj(), oldStyleName, newStyleName); + } + return true; + } + +/*********************************************************************** +DocumentModel::ClearStyle +***********************************************************************/ + + bool DocumentModel::ClearStyle(TextPos begin, TextPos end) + { + return EditContainer(begin, end, [=](DocumentParagraphRun* paragraph, RunRangeMap& runRanges, vint start, vint end) + { + document_editor::ClearStyle(paragraph, runRanges, start, end); + }); + } + +/*********************************************************************** +DocumentModel::ClearStyle +***********************************************************************/ + + Ptr DocumentModel::SummarizeStyle(TextPos begin, TextPos end) + { + Ptr style; + RunRangeMap runRanges; + + if (begin == end) goto END_OF_SUMMERIZING; + + // check caret range + if (!CheckEditRange(begin, end, runRanges)) return nullptr; + + // Summarize container + if (begin.row == end.row) + { + style = document_editor::SummarizeStyle(paragraphs[begin.row].Obj(), runRanges, this, begin.column, end.column); + } + else + { + for (vint i = begin.row; i <= end.row; i++) + { + Ptr paragraph = paragraphs[i]; + if (begin.row < i && i < end.row) + { + GetRunRange(paragraph.Obj(), runRanges); + } + RunRange range = runRanges[paragraph.Obj()]; + Ptr paragraphStyle; + if (i == begin.row) + { + paragraphStyle = document_editor::SummarizeStyle(paragraph.Obj(), runRanges, this, begin.column, range.end); + } + else if (i == end.row) + { + paragraphStyle = document_editor::SummarizeStyle(paragraph.Obj(), runRanges, this, range.start, end.column); + } + else + { + paragraphStyle = document_editor::SummarizeStyle(paragraph.Obj(), runRanges, this, range.start, range.end); + } + + if (!style) + { + style = paragraphStyle; + } + else if (paragraphStyle) + { + AggregateStyle(style, paragraphStyle); + } + } + } + + END_OF_SUMMERIZING: + if (!style) + { + style = new DocumentStyleProperties; + } + return style; + } + + Nullable DocumentModel::SummarizeStyleName(TextPos begin, TextPos end) + { + if (begin == end) return {}; + + // check caret range + RunRangeMap runRanges; + if (!CheckEditRange(begin, end, runRanges)) return {}; + + // Summarize container + Nullable styleName; + + if (begin.row == end.row) + { + styleName = document_editor::SummarizeStyleName(paragraphs[begin.row].Obj(), runRanges, this, begin.column, end.column); + } + else + { + for (vint i = begin.row; i <= end.row; i++) + { + Ptr paragraph = paragraphs[i]; + if (begin.row < i && i < end.row) + { + GetRunRange(paragraph.Obj(), runRanges); + } + RunRange range = runRanges[paragraph.Obj()]; + Nullable newStyleName; + if (i == begin.row) + { + newStyleName = document_editor::SummarizeStyleName(paragraph.Obj(), runRanges, this, begin.column, range.end); + } + else if (i == end.row) + { + newStyleName = document_editor::SummarizeStyleName(paragraph.Obj(), runRanges, this, range.start, end.column); + } + else + { + newStyleName = document_editor::SummarizeStyleName(paragraph.Obj(), runRanges, this, range.start, end.column); + } + + if (i == begin.row) + { + styleName = newStyleName; + } + else if (!styleName || !newStyleName || styleName.Value() != newStyleName.Value()) + { + styleName = Nullable(); + } + } + } + return styleName; + } + + Nullable DocumentModel::SummarizeParagraphAlignment(TextPos begin, TextPos end) + { + bool left = false; + bool center = false; + bool right = false; + + RunRangeMap runRanges; + if (!CheckEditRange(begin, end, runRanges)) return {}; + + for (vint i = begin.row; i <= end.row; i++) + { + auto paragraph = paragraphs[i]; + if (paragraph->alignment) + { + switch (paragraph->alignment.Value()) + { + case Alignment::Left: + left = true; + break; + case Alignment::Center: + center = true; + break; + case Alignment::Right: + right = true; + break; + } + } + else + { + left = true; + } + } + + if (left && !center && !right) return Alignment::Left; + if (!left && center && !right) return Alignment::Center; + if (!left && !center && right) return Alignment::Right; + return {}; + } + } +} + + +/*********************************************************************** +.\RESOURCES\GUIDOCUMENT_LOAD.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + using namespace parsing::tabling; + using namespace parsing::xml; + using namespace regex; + +/*********************************************************************** +document_operation_visitors::DeserializeNodeVisitor +***********************************************************************/ + + namespace document_operation_visitors + { + class DeserializeNodeVisitor : public XmlNode::IVisitor + { + public: + Ptr model; + Ptr container; + vint paragraphIndex; + Ptr resource; + Ptr resolver; + Regex regexAttributeApply; + GuiResourceError::List& errors; + + DeserializeNodeVisitor(Ptr _model, Ptr _paragraph, vint _paragraphIndex, Ptr _resource, Ptr _resolver, GuiResourceError::List& _errors) + :model(_model) + , container(_paragraph) + , paragraphIndex(_paragraphIndex) + , resource(_resource) + , resolver(_resolver) + , regexAttributeApply(L"/{@([^{}]+)/}") + , errors(_errors) + { + } + + void PrintText(const WString& text) + { + Ptr run = new DocumentTextRun; + run->text = text; + container->runs.Add(run); + } + + void Visit(XmlText* node)override + { + PrintText(node->content.value); + } + + void Visit(XmlCData* node)override + { + PrintText(node->content.value); + } + + void Visit(XmlAttribute* node)override + { + } + + void Visit(XmlComment* node)override + { + } + + void Visit(XmlElement* node)override + { + Ptr createdContainer; + bool useTemplateInfo = false; + XmlElement* subNodeContainer = node; + + if (node->name.value == L"br") + { + PrintText(L"\r\n"); + } + else if (node->name.value == L"sp") + { + PrintText(L" "); + } + else if (node->name.value == L"tab") + { + PrintText(L"\t"); + } + else if (node->name.value == L"img") + { + Ptr run = new DocumentImageRun; + run->baseline = -1; + + if (Ptr source = XmlGetAttribute(node, L"source")) + { + run->source = source->value.value; + WString protocol, path; + if (IsResourceUrl(run->source, protocol, path)) + { + Ptr imageData = resolver->ResolveResource(protocol, path).Cast(); + if (imageData) + { + run->image = imageData->GetImage(); + } + if (run->image && run->image->GetFrameCount() > 0) + { + run->size = run->image->GetFrame(0)->GetSize(); + run->frameIndex = 0; + } + } + + FOREACH(Ptr, att, node->attributes) + { + if (att->name.value == L"width") + { + run->size.x = wtoi(att->value.value); + } + else if (att->name.value == L"height") + { + run->size.y = wtoi(att->value.value); + } + else if (att->name.value == L"baseline") + { + run->baseline = wtoi(att->value.value); + } + else if (att->name.value == L"frameIndex") + { + run->frameIndex = wtoi(att->value.value); + } + else if (att->name.value != L"source") + { + errors.Add(GuiResourceError({ {resource},att->name.codeRange.start }, L"Unknown attribute in : \"" + att->name.value + L"\".")); + } + } + + container->runs.Add(run); + } + else + { + errors.Add(GuiResourceError({ {resource},node->codeRange.start }, L"Attribute \"source\" is missing in .")); + } + } + else if (node->name.value == L"object") + { + Ptr run = new DocumentEmbeddedObjectRun; + run->baseline = -1; + + if (auto name = XmlGetAttribute(node, L"name")) + { + run->name = name->value.value; + container->runs.Add(run); + } + else + { + errors.Add(GuiResourceError({ {resource},node->codeRange.start }, L"The \"name\" attribute in is missing.")); + } + } + else if (node->name.value == L"font") + { + Ptr run = new DocumentStylePropertiesRun(); + Ptr sp = new DocumentStyleProperties; + run->style = sp; + + FOREACH(Ptr, att, node->attributes) + { + if (att->name.value == L"face") + { + sp->face = att->value.value; + } + else if (att->name.value == L"size") + { + sp->size = DocumentFontSize::Parse(att->value.value); + } + else if (att->name.value == L"color") + { + sp->color = Color::Parse(att->value.value); + } + else if (att->name.value == L"bkcolor") + { + sp->backgroundColor = Color::Parse(att->value.value); + } + else + { + errors.Add(GuiResourceError({ {resource},att->name.codeRange.start }, L"Unknown attribute in : \"" + att->name.value + L"\".")); + } + } + container->runs.Add(run); + createdContainer = run; + } + else if (node->name.value == L"b" || node->name.value == L"b-") + { + Ptr run = new DocumentStylePropertiesRun(); + run->style = new DocumentStyleProperties; + run->style->bold = node->name.value == L"b"; + container->runs.Add(run); + createdContainer = run; + } + else if (node->name.value == L"i" || node->name.value == L"i-") + { + Ptr run = new DocumentStylePropertiesRun(); + run->style = new DocumentStyleProperties; + run->style->italic = node->name.value == L"i"; + container->runs.Add(run); + createdContainer = run; + } + else if (node->name.value == L"u" || node->name.value == L"u-") + { + Ptr run = new DocumentStylePropertiesRun(); + run->style = new DocumentStyleProperties; + run->style->underline = node->name.value == L"u"; + container->runs.Add(run); + createdContainer = run; + } + else if (node->name.value == L"s" || node->name.value == L"s-") + { + Ptr run = new DocumentStylePropertiesRun(); + run->style = new DocumentStyleProperties; + run->style->strikeline = node->name.value == L"s"; + container->runs.Add(run); + createdContainer = run; + } + else if (node->name.value == L"ha") + { + Ptr run = new DocumentStylePropertiesRun(); + run->style = new DocumentStyleProperties; + run->style->antialias = true; + run->style->verticalAntialias = false; + container->runs.Add(run); + createdContainer = run; + } + else if (node->name.value == L"va") + { + Ptr run = new DocumentStylePropertiesRun(); + run->style = new DocumentStyleProperties; + run->style->antialias = true; + run->style->verticalAntialias = true; + container->runs.Add(run); + createdContainer = run; + } + else if (node->name.value == L"na") + { + Ptr run = new DocumentStylePropertiesRun(); + run->style = new DocumentStyleProperties; + run->style->antialias = false; + run->style->verticalAntialias = false; + container->runs.Add(run); + createdContainer = run; + } + else if (node->name.value == L"div") + { + if (Ptr att = XmlGetAttribute(node, L"style")) + { + WString styleName = att->value.value; + + Ptr run = new DocumentStyleApplicationRun; + run->styleName = styleName; + container->runs.Add(run); + createdContainer = run; + } + else + { + createdContainer = container; + } + } + else if (node->name.value == L"a") + { + Ptr run = new DocumentHyperlinkRun; + run->normalStyleName = L"#NormalLink"; + run->activeStyleName = L"#ActiveLink"; + if (Ptr att = XmlGetAttribute(node, L"normal")) + { + run->normalStyleName = att->value.value; + } + if (Ptr att = XmlGetAttribute(node, L"active")) + { + run->activeStyleName = att->value.value; + } + if (Ptr att = XmlGetAttribute(node, L"href")) + { + run->reference = att->value.value; + } + run->styleName = run->normalStyleName; + container->runs.Add(run); + createdContainer = run; + } + else if (node->name.value == L"p") + { + FOREACH(Ptr, sub, node->subNodes) + { + sub->Accept(this); + } + } + else + { + if (node->name.value != L"nop") + { + errors.Add(GuiResourceError({ {resource},node->codeRange.start }, L"Unknown element in

: \"" + node->name.value + L"\".")); + } + FOREACH(Ptr, sub, node->subNodes) + { + sub->Accept(this); + } + } + + if (createdContainer) + { + Ptr oldContainer = container; + container = createdContainer; + FOREACH(Ptr, subNode, subNodeContainer->subNodes) + { + subNode->Accept(this); + } + container = oldContainer; + } + } + + void Visit(XmlInstruction* node)override + { + } + + void Visit(XmlDocument* node)override + { + } + }; + + Ptr ParseDocumentStyle(Ptr resource, Ptr styleElement, GuiResourceError::List& errors) + { + Ptr style=new DocumentStyle; + + if(Ptr parent=XmlGetAttribute(styleElement, L"parent")) + { + style->parentStyleName=parent->value.value; + } + + Ptr sp=new DocumentStyleProperties; + style->styles=sp; + + FOREACH(Ptr, att, XmlGetElements(styleElement)) + { + if(att->name.value==L"face") + { + sp->face=XmlGetValue(att); + } + else if(att->name.value==L"size") + { + sp->size=DocumentFontSize::Parse(XmlGetValue(att)); + } + else if(att->name.value==L"color") + { + sp->color=Color::Parse(XmlGetValue(att)); + } + else if(att->name.value==L"bkcolor") + { + sp->backgroundColor=Color::Parse(XmlGetValue(att)); + } + else if(att->name.value==L"b") + { + sp->bold=XmlGetValue(att)==L"true"; + } + else if(att->name.value==L"i") + { + sp->italic=XmlGetValue(att)==L"true"; + } + else if(att->name.value==L"u") + { + sp->underline=XmlGetValue(att)==L"true"; + } + else if(att->name.value==L"s") + { + sp->strikeline=XmlGetValue(att)==L"true"; + } + else if(att->name.value==L"antialias") + { + WString value=XmlGetValue(att); + if(value==L"horizontal" || value==L"default") + { + sp->antialias=true; + sp->verticalAntialias=false; + } + else if(value==L"no") + { + sp->antialias=false; + sp->verticalAntialias=false; + } + else if(value==L"vertical") + { + sp->antialias=true; + sp->verticalAntialias=true; + } + } + else + { + errors.Add(GuiResourceError({ {resource},att->codeRange.start }, L"Unknown element in