feat: add option to see generated XML directly from UI Designer

This commit is contained in:
Akash Yadav
2022-12-11 19:08:56 +05:30
parent 1cb6c1a005
commit 1ff2658d1f
22 changed files with 483 additions and 47 deletions
Binary file not shown.
@@ -13,8 +13,8 @@ import com.itsaky.androidide.PreferencesActivity
import com.itsaky.androidide.TerminalActivity
import com.itsaky.androidide.adapters.MainActionsListAdapter
import com.itsaky.androidide.app.BaseApplication
import com.itsaky.androidide.common.databinding.LayoutDialogProgressBinding
import com.itsaky.androidide.databinding.FragmentMainBinding
import com.itsaky.androidide.databinding.LayoutDialogProgressBinding
import com.itsaky.androidide.fragments.WizardFragment.OnProjectCreatedListener
import com.itsaky.androidide.models.MainScreenAction
import com.itsaky.androidide.preferences.databinding.LayoutDialogTextInputBinding
@@ -154,7 +154,7 @@ class FileTreeActionHandler : BaseEventHandler() {
private fun createFileOptionsFragment(file: File): OptionsListFragment {
val fragment = OptionsListFragment()
fragment.addOption(
SheetOption(ID_COPY_PATH, R.drawable.ic_file_copy_path, string.copy_path, file)
SheetOption(ID_COPY_PATH, R.drawable.ic_copy, string.copy_path, file)
)
fragment.addOption(
SheetOption(ID_RENAME_FILE, R.drawable.ic_file_rename, string.rename_file, file)
@@ -18,7 +18,10 @@ package com.itsaky.androidide.utils
import android.content.Context
import android.content.DialogInterface.OnClickListener
import android.view.LayoutInflater
import android.view.View
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.itsaky.androidide.common.databinding.LayoutDialogProgressBinding
import com.itsaky.androidide.resources.R.string
import com.itsaky.androidide.resources.R.style
import org.jetbrains.annotations.Contract
@@ -29,7 +32,37 @@ import org.jetbrains.annotations.Contract
* @author Akash Yadav
*/
object DialogUtils {
@JvmStatic
@JvmOverloads
fun newProgressDialog(
context: Context,
title: String,
message: String? = null,
cancelable: Boolean = false,
onCancelClick: OnClickListener? = null
): MaterialAlertDialogBuilder {
val binding = LayoutDialogProgressBinding.inflate(LayoutInflater.from(context))
val builder = newMaterialDialogBuilder(context)
builder.setTitle(title)
builder.setView(binding.root)
builder.setCancelable(cancelable)
if (message != null) {
binding.message.text = message
binding.message.visibility = View.VISIBLE
}
if (onCancelClick != null) {
builder.setPositiveButton(android.R.string.cancel) { dialog, which ->
dialog.dismiss()
onCancelClick.onClick(dialog, which)
}
}
return builder
}
/**
* Create a new alert dialog with two buttons: <span>Yes</span> and <span>No</span>. This method
* simply calls [.newYesNoDialog] with default values for title and message.
@@ -53,9 +86,10 @@ object DialogUtils {
context.getString(string.msg_yesno_def_title),
context.getString(string.msg_yesno_def_message),
positiveClickListener,
negativeClickListener)
negativeClickListener
)
}
/**
* Create a new alert dialog with two buttons: <span>Yes</span> and <span>No</span>.
*
@@ -84,7 +118,7 @@ object DialogUtils {
builder.setNegativeButton(string.no, negativeClickListener)
return builder
}
/**
* Creates a new MaterialAlertDialogBuilder with the app's default style.
*
@@ -96,4 +130,4 @@ object DialogUtils {
fun newMaterialDialogBuilder(context: Context): MaterialAlertDialogBuilder {
return MaterialAlertDialogBuilder(context, style.AppTheme_MaterialAlertDialog)
}
}
}
+4 -3
View File
@@ -77,11 +77,11 @@ ext {
lsp4j : 'org.eclipse.lsp4j:org.eclipse.lsp4j:0.12.0',
jb_annotations: 'org.jetbrains:annotations:23.0.0'
]
git = [
jgit : 'org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r'
jgit: 'org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r'
]
tests = [
junit : 'junit:junit:4.13.2',
google_truth : 'com.google.truth:truth:1.1.3',
@@ -89,5 +89,6 @@ ext {
androidx_test_core: 'androidx.test:core:1.5.0',
androidx_junit : 'androidx.test.ext:junit:1.1.4',
androidx_espresso : 'androidx.test.espresso:espresso-core:3.5.0',
mockito_kotlin : 'org.mockito.kotlin:mockito-kotlin:4.1.0'
]
}
@@ -18,7 +18,6 @@
package com.itsaky.androidide.lsp.xml.providers.format;
import com.itsaky.androidide.lsp.models.TextEdit;
import com.itsaky.androidide.lsp.xml.models.XMLServerSettings;
import com.itsaky.androidide.models.Range;
import com.itsaky.androidide.utils.ILogger;
@@ -27,27 +26,25 @@ import org.eclipse.lemminx.dom.DOMDocument;
import java.util.List;
/**
* XML formatter support.
*
*/
/** XML formatter support. */
public class XMLFormatter {
private static final ILogger LOG = ILogger.newInstance("XMLFormatter");
/**
* Returns a List containing a single TextEdit, containing the newly formatted
* changes of the document.
* Returns a List containing a single TextEdit, containing the newly formatted changes of the
* document.
*
* @param range specified range in which formatting will be done
* @param range specified range in which formatting will be done
* @return List containing a TextEdit with formatting changes
*/
public List<? extends TextEdit> format(DOMDocument xmlDocument, Range range) {
try {
XMLFormatterDocument formatterDocument = new XMLFormatterDocument(xmlDocument.getTextDocument(), range);
XMLFormatterDocument formatterDocument =
new XMLFormatterDocument(xmlDocument.getTextDocument(), range);
return formatterDocument.format();
} catch (BadLocationException e) {
LOG.error("Formatting failed due to BadLocation", e);
}
return null;
}
}
}
@@ -44,9 +44,7 @@ public class XMLBuilder {
private final String lineDelimiter;
private final StringBuilder xml;
private final String whitespacesIndent;
private static final Logger LOGGER = Logger.getLogger(XMLBuilder.class.getName());
public XMLBuilder(String whitespacesIndent, String lineDelimiter) {
this.whitespacesIndent = whitespacesIndent;
this.lineDelimiter = lineDelimiter;
@@ -122,11 +120,11 @@ public class XMLBuilder {
public XMLBuilder addSingleAttribute(
DOMAttr attr, boolean surroundWithQuotes, boolean prependSpace) {
return addSingleAttribute(
attr.getName(), attr.getOriginalValue(), surroundWithQuotes, prependSpace, attr);
attr.getName(), attr.getOriginalValue(), surroundWithQuotes, prependSpace);
}
public XMLBuilder addSingleAttribute(String name, String value, boolean surroundWithQuotes) {
return addSingleAttribute(name, value, surroundWithQuotes, true, null);
return addSingleAttribute(name, value, surroundWithQuotes, true);
}
/**
@@ -140,7 +138,7 @@ public class XMLBuilder {
* @return this XML Builder
*/
private XMLBuilder addSingleAttribute(
String name, String value, boolean surroundWithQuotes, boolean prependSpace, DOMAttr attr) {
String name, String value, boolean surroundWithQuotes, boolean prependSpace) {
if (prependSpace) {
appendSpace();
}
Binary file not shown.
+2
View File
@@ -46,6 +46,7 @@ dependencies {
implementation project(path: ':actions')
implementation project(path: ':common')
implementation project(path: ':editor')
implementation project(path: ':logger')
implementation project(path: ':lookup')
implementation project(path: ':lsp:api')
@@ -56,4 +57,5 @@ dependencies {
testImplementation tests.junit
testImplementation tests.google_truth
testImplementation tests.robolectric
testImplementation tests.mockito_kotlin
}
+4 -1
View File
@@ -17,9 +17,12 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<application android:theme="@style/Theme.AndroidIDE">
<activity
android:name=".UIDesignerActivity"
android:windowSoftInputMode="adjustResize" />
<activity
android:name=".ShowXmlActivity"
android:windowSoftInputMode="stateAlwaysHidden" />
</application>
</manifest>
@@ -0,0 +1,107 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.uidesigner
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import com.blankj.utilcode.util.ReflectUtils
import com.itsaky.androidide.app.BaseIDEActivity
import com.itsaky.androidide.editor.IEditorProvider
import com.itsaky.androidide.lookup.Lookup
import com.itsaky.androidide.preferences.internal.fontSize
import com.itsaky.androidide.syntax.colorschemes.SchemeAndroidIDE
import com.itsaky.androidide.uidesigner.databinding.ActivityShowXmlBinding
import com.itsaky.androidide.utils.jetbrainsMono
import io.github.rosemoe.sora.event.ScrollEvent
import io.github.rosemoe.sora.event.Unsubscribe
import io.github.rosemoe.sora.widget.CodeEditor
/**
* An activity to show the XML code generated by UI Designer.
*
* @author Akash Yadav
*/
class ShowXmlActivity : BaseIDEActivity() {
private var editor: CodeEditor? = null
private var unsubscribe: Unsubscribe? = null
private var binding: ActivityShowXmlBinding? = null
companion object {
const val KEY_XML = "ide.uidesigner.viewXml.Xml"
}
override fun bindLayout(): View {
this.binding = ActivityShowXmlBinding.inflate(layoutInflater)
this.editor =
Lookup.DEFAULT.lookup(IEditorProvider::class.java)!!.createEditor(this) as CodeEditor
setSupportActionBar(this.binding!!.toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
this.binding!!.toolbar.setNavigationOnClickListener { finish() }
this.editor?.let { editor ->
editor.colorScheme = SchemeAndroidIDE.newInstance(this@ShowXmlActivity)
editor.editable = false
editor.typefaceText = jetbrainsMono()
editor.typefaceLineNumber = jetbrainsMono()
// TODO(itsaky): Move language implementations to separate module
editor.setEditorLanguage(
ReflectUtils.reflect("com.itsaky.androidide.language.xml.XMLLanguage").newInstance().get()
)
editor.setText(intent?.getStringExtra(KEY_XML) ?: "")
editor.setTextSize(fontSize)
editor.subscribeEvent(ScrollEvent::class.java) { event, unsubscribe ->
this.unsubscribe = unsubscribe
val fab = this.binding?.copy ?: return@subscribeEvent
event.apply {
if (startY > endY && fab.isExtended) {
fab.shrink()
}
if (endY == 0 || startY < endY && !fab.isExtended) {
fab.extend()
}
}
}
binding!!.editorContainer.let { container ->
container.removeAllViews()
container.addView(editor, ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT))
}
}
return this.binding!!.root
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onDestroy() {
super.onDestroy()
this.binding = null
this.unsubscribe?.unsubscribe()
this.editor?.release()
this.editor = null
}
}
@@ -0,0 +1,103 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.uidesigner.actions
import android.content.Context
import android.content.Intent
import androidx.core.content.ContextCompat
import com.blankj.utilcode.util.ThreadUtils
import com.itsaky.androidide.actions.ActionData
import com.itsaky.androidide.inflater.internal.ViewImpl
import com.itsaky.androidide.inflater.viewGroup
import com.itsaky.androidide.tasks.executeAsyncProvideError
import com.itsaky.androidide.uidesigner.R
import com.itsaky.androidide.uidesigner.ShowXmlActivity
import com.itsaky.androidide.uidesigner.utils.ViewToXml
import com.itsaky.androidide.utils.DialogUtils
import com.itsaky.androidide.utils.ILogger
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
/**
* Navigates the user to [ShowXmlActivity].
*
* @author Akash Yadav
*/
class ShowXmlAction(context: Context) : UiDesignerAction() {
override val id: String = "ide.uidesigner.showXml"
private val log = ILogger.newInstance("ShowXmlAction")
init {
label = context.getString(R.string.xml)
icon = ContextCompat.getDrawable(context, R.drawable.ic_language_xml)
}
override fun execAction(data: ActionData): Any {
data.requireActivity().apply {
val workspace = data.requireWorkspace().workspaceView
val future: CompletableFuture<String?> =
executeAsyncProvideError({
if (workspace.childCount == 0) {
throw CompletionException(
IllegalStateException("No views have been added to workspace")
)
}
if (workspace.childCount > 1) {
throw CompletionException(
IllegalStateException("Invalid view hierarchy. More than one root views found.")
)
}
return@executeAsyncProvideError ViewToXml.generateXml(workspace[0] as ViewImpl)
}) { _, _ ->
}
val progress =
DialogUtils.newProgressDialog(
this,
getString(R.string.title_generating_xml),
getString(R.string.please_wait),
false
) { _, _ ->
future.cancel(true)
}
.show()
future.whenComplete { result, error ->
ThreadUtils.runOnUiThread {
progress.dismiss()
if (result.isNullOrBlank() || error != null) {
log.error("Unable to generate XML code", error)
return@runOnUiThread
}
val intent =
Intent(this, ShowXmlActivity::class.java).apply {
putExtra(ShowXmlActivity.KEY_XML, result)
}
startActivity(intent)
}
}
}
return true
}
}
@@ -25,6 +25,7 @@ fun registerUiDesignerActions(context: Context) {
ActionsRegistry.getInstance().apply {
registerAction(UndoAction(context))
registerAction(RedoAction(context))
registerAction(ShowXmlAction(context))
}
}
@@ -117,16 +117,18 @@ class DesignerWorkspaceFragment : BaseFragment() {
var hasError = false
val inflated =
try {
inflater.inflate(viewModel.file, binding!!.workspace)
inflater.inflate(viewModel.file, workspaceView)
} catch (e: Throwable) {
log.error(e)
viewModel.errText = "${e.message}${e.cause?.message?.let { "\n$it" } ?: ""}"
hasError = true
emptyList()
}
if (inflated.isEmpty() && !hasError) {
viewModel.errText = getString(R.string.msg_empty_ui_layout)
}
binding!!
.workspace
.setOnDragListener(WidgetDragListener(workspaceView, this.placeholder, touchSlop))
@@ -0,0 +1,84 @@
/*
* This file is part of AndroidIDE.
*
* AndroidIDE is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* AndroidIDE is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
*/
package com.itsaky.androidide.uidesigner.utils
import com.itsaky.androidide.inflater.IView
import com.itsaky.androidide.inflater.IViewGroup
import com.itsaky.androidide.inflater.internal.ViewImpl
import com.itsaky.androidide.lsp.xml.utils.XMLBuilder
/**
* Generates XML code for [IView].
*
* @author Akash Yadav
*/
object ViewToXml {
@JvmStatic
fun generateXml(view: ViewImpl): String {
val builder = XMLBuilder("", System.lineSeparator())
builder.apply {
appendXmlProlog()
linefeed()
appendView(view)
}
return builder.toString()
}
private fun XMLBuilder.appendView(view: ViewImpl, indent: Int = 1) {
startElement(view.tag, false)
for (namespace in view.namespaceDecls) {
linefeed()
indent(indent)
addSingleAttribute("xmlns:${namespace.prefix}", namespace.uri, true)
}
for (attr in view.attributes) {
linefeed()
indent(indent)
addSingleAttribute("${attr.namespace.prefix}:${attr.name}", attr.value, true)
}
if (view is IViewGroup) {
closeStartElement()
for (i in 0 until view.childCount) {
linefeed()
linefeed()
indent(indent)
appendView(view[i] as ViewImpl, indent + 1)
}
linefeed()
linefeed()
endElement(view.tag)
} else {
selfCloseElement()
}
}
private fun XMLBuilder.appendXmlProlog() {
startPrologOrPI("xml")
addSingleAttribute("version", "1.0", true)
addSingleAttribute("encoding", "utf-8", true)
endPrologOrPI()
}
}
@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ This file is part of AndroidIDE.
~
~ AndroidIDE is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ (at your option) any later version.
~
~ AndroidIDE is distributed in the hope that it will be useful,
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
~ GNU General Public License for more details.
~
~ You should have received a copy of the GNU General Public License
~ along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
-->
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$Behavior">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/xml" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/editor_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingTop="16dp"
android:paddingEnd="8dp"
android:paddingBottom="16dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/copy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="28dp"
android:text="@string/copy"
app:icon="@drawable/ic_copy" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
@@ -55,6 +55,20 @@ interface ILayoutInflater : Closeable {
*/
fun inflate(file: File, parent: ViewGroup): List<IView>
/**
* Inflate the given raw XML file.
*
* **NOTE** : The [startParse][com.itsaky.androidide.inflater.utils.startParse] method is called
* if it hasn't been called yet. However, the [endParse]
* [com.itsaky.androidide.inflater.utils.endParse] method is not called after the parse is done.
* The caller is expected to call [close] when this inflater instance is no longer needed. The
* [close] method calls the [endParse] [com.itsaky.androidide.inflater.utils.endParse].
*
* @param file The file to inflate.
* @param parent The parent view.
*/
fun inflate(file: File, parent: IViewGroup): List<IView>
fun closeSilently() {
try {
close()
@@ -62,7 +76,7 @@ interface ILayoutInflater : Closeable {
// ignored
}
}
companion object {
/** The [Key] that can be used to lookup the layout inflater service. */
@@ -41,6 +41,9 @@ interface IView {
/** The parent view. */
var parent: IViewGroup?
/** The namespaces that have been declared in this view. */
val namespaceDecls: Collection<INamespace>
/** The attributes of this view. */
val attributes: List<IAttribute>
@@ -86,20 +86,16 @@ open class LayoutInflaterImpl : ILayoutInflater {
get() = this._currentLayoutFile!!
override fun inflate(file: File, parent: ViewGroup): List<IView> {
this._primaryInflatingFile = file
IDTable.newRound()
if (!isParsing) {
if (this.module == null) {
startParse(file)
this.module = com.itsaky.androidide.inflater.utils.module
} else {
startParse(this.module!!)
}
}
inflationEventListener?.onEvent(InflationStartEvent())
startInflation(file)
return doInflate(file, parent).apply {
inflationEventListener?.onEvent(InflationFinishEvent(this))
_primaryInflatingFile = null
finishInflation()
}
}
override fun inflate(file: File, parent: IViewGroup) : List<IView> {
startInflation(file)
return doInflate(file, parent).apply {
finishInflation()
}
}
@@ -115,6 +111,11 @@ open class LayoutInflaterImpl : ILayoutInflater {
val (processor, module) = processXmlFile(file)
return doInflate(processor, parent, module)
}
protected open fun doInflate(file: File, parent: IViewGroup): List<IView> {
val (processor, module) = processXmlFile(file)
return doInflate(processor, parent, module)
}
protected open fun doInflate(
processor: XmlProcessor,
@@ -123,6 +124,14 @@ open class LayoutInflaterImpl : ILayoutInflater {
): List<IView> {
return doInflate(processor, module) { wrap(parent) }
}
protected open fun doInflate(
processor: XmlProcessor,
parent: IViewGroup,
module: AndroidModule,
): List<IView> {
return doInflate(processor, module) { parent }
}
protected open fun doInflate(
processor: XmlProcessor,
@@ -320,7 +329,7 @@ open class LayoutInflaterImpl : ILayoutInflater {
protected open fun addNamespaceDecls(element: XmlElement, view: ViewImpl) {
if (element.namespaceDeclarationCount > 0) {
for (xmlNamespace in element.namespaceDeclarationList) {
view.namespaceDecls[xmlNamespace.uri] = NamespaceImpl(xmlNamespace.prefix, xmlNamespace.uri)
view.namespaces[xmlNamespace.uri] = NamespaceImpl(xmlNamespace.prefix, xmlNamespace.uri)
}
}
}
@@ -354,6 +363,25 @@ open class LayoutInflaterImpl : ILayoutInflater {
private fun onCreateUnsupportedView(message: String, parent: ViewGroup): IView {
return ErrorView(currentLayoutFile, parent.context, message)
}
private fun List<IView>.finishInflation() {
inflationEventListener?.onEvent(InflationFinishEvent(this))
_primaryInflatingFile = null
}
private fun startInflation(file: File) {
this._primaryInflatingFile = file
IDTable.newRound()
if (!isParsing) {
if (this.module == null) {
startParse(file)
this.module = com.itsaky.androidide.inflater.utils.module
} else {
startParse(this.module!!)
}
}
inflationEventListener?.onEvent(InflationStartEvent())
}
private fun wrap(parent: ViewGroup): IViewGroup {
return componentFactory.createView(currentLayoutFile, parent.javaClass.name, parent)
@@ -46,9 +46,11 @@ constructor(
private val attrChangeListeners = mutableListOf<AttributeChangeListener>()
private val _attributes = mutableListOf<IAttribute>()
internal val namespaceDecls = mutableMapOf<String, INamespace>()
internal val namespaces = mutableMapOf<String, INamespace>()
override var parent: IViewGroup? = null
override val namespaceDecls: Collection<INamespace>
get() = this.namespaces.values
override val attributes: List<IAttribute>
get() = this._attributes
@@ -116,7 +118,7 @@ constructor(
fun findNamespaces(): Set<INamespace> {
return hashSetOf<INamespace>().apply {
addAll(namespaceDecls.values)
addAll(namespaces.values)
if (parent is ViewImpl) {
(parent as? ViewImpl)?.findNamespaces()?.let { addAll(it) }
}
@@ -124,7 +126,7 @@ constructor(
}
fun findNamespaceByUri(uri: String): INamespace? {
return this.namespaceDecls[uri] ?: (parent as? ViewImpl)?.findNamespaceByUri(uri)
return this.namespaces[uri] ?: (parent as? ViewImpl)?.findNamespaceByUri(uri)
}
open fun immutable() : IView {