Example using an UIList based on the answer of Create an interface which is similar to the material list box? and my modification for Displaying text inside an addon Panel to get some kind of log window as part of a popup. I've removed all superfluous operators and added a CUSTOM_OT_popup
operator calling invoke_props_dialog
in its invoke
method as well as a check
method for proper drawing:

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
bl_info = {
"name": "log-uilist-dev",
"description": "",
"author": "p2or, brockmann",
"version": (0, 1),
"blender": (2, 80, 0),
"location": "Text Editor",
"warning": "", # used for warning icon and text in addons panel
"wiki_url": "",
"tracker_url": "",
"category": "Development"
}
import bpy
from bpy.props import (IntProperty,
BoolProperty,
StringProperty,
EnumProperty,
CollectionProperty)
from bpy.types import (Operator,
Panel,
PropertyGroup,
UIList)
# -------------------------------------------------------------------
# Operators
# -------------------------------------------------------------------
class CUSTOM_OT_actions(Operator):
"""Move items up and down, add and remove"""
bl_idname = "custom.list_action"
bl_label = "List Actions"
bl_description = "Move items up and down, add and remove"
bl_options = {'REGISTER'}
action: EnumProperty(
items=(
('UP', "Up", ""),
('DOWN', "Down", ""),
('REMOVE', "Remove", ""),
('ADD', "Add", "")))
list_item: StringProperty()
def invoke(self, context, event):
scn = context.scene
idx = scn.custom_index
try:
item = scn.custom[idx]
except IndexError:
pass
else:
if self.action == 'DOWN' and idx < len(scn.custom) - 1:
item_next = scn.custom[idx+1].name
scn.custom.move(idx, idx+1)
scn.custom_index += 1
info = 'Item "%s" moved to position %d' % (item.name, scn.custom_index + 1)
self.report({'INFO'}, info)
elif self.action == 'UP' and idx >= 1:
item_prev = scn.custom[idx-1].name
scn.custom.move(idx, idx-1)
scn.custom_index -= 1
info = 'Item "%s" moved to position %d' % (item.name, scn.custom_index + 1)
self.report({'INFO'}, info)
elif self.action == 'REMOVE':
info = 'Item "%s" removed from list' % (scn.custom[idx].name)
scn.custom_index -= 1
scn.custom.remove(idx)
self.report({'INFO'}, info)
if self.action == 'ADD':
if self.list_item:
item = scn.custom.add()
item.name = self.list_item
item.coll_id = len(scn.custom)
scn.custom_index = len(scn.custom)-1
info = '"%s" added to list' % (item.name)
self.report({'INFO'}, info)
return {"FINISHED"}
class CUSTOM_OT_clearList(Operator):
"""Clear all items of the list"""
bl_idname = "custom.clear_list"
bl_label = "Clear List"
bl_description = "Clear all items of the list"
bl_options = {'INTERNAL'}
@classmethod
def poll(cls, context):
return bool(context.scene.custom)
def invoke(self, context, event):
return context.window_manager.invoke_confirm(self, event)
def execute(self, context):
if bool(context.scene.custom):
context.scene.custom.clear()
self.report({'INFO'}, "All items removed")
else:
self.report({'INFO'}, "Nothing to remove")
return{'FINISHED'}
# -------------------------------------------------------------------
# Drawing
# -------------------------------------------------------------------
class CUSTOM_UL_items(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
split = layout.split(factor=0.2)
split.label(text="%d" % (index))
#split.prop(item, "name", text="", emboss=False, translate=False, icon=custom_icon)
split.label(text=item.name) # avoids renaming the item by accident
def invoke(self, context, event):
pass
class CUSTOM_OT_popup(Operator):
bl_idname = "custom.call_popup"
bl_label = "Log Window"
bl_options = {'REGISTER'}
@classmethod
def poll(cls, context):
return True
def execute(self, context):
return {'FINISHED'}
def check(self, context):
return True
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self, width=500)
def draw(self, context):
layout = self.layout
scn = bpy.context.scene
rows = 7
row = layout.row()
row.template_list("CUSTOM_UL_items", "", scn, "custom", scn, "custom_index", rows=rows)
row = layout.row()
add = row.operator(CUSTOM_OT_actions.bl_idname, icon="ADD", text="Add Log Line")
add.list_item = "Error %d" % (len(scn.custom))
add.action = 'ADD'
row = layout.row().operator("custom.clear_list", icon="X")
row = layout.row()
# -------------------------------------------------------------------
# Collection
# -------------------------------------------------------------------
class CUSTOM_objectCollection(PropertyGroup):
#name: StringProperty() -> Instantiated by default
coll_type: StringProperty()
coll_id: IntProperty()
# -------------------------------------------------------------------
# Register & Unregister
# -------------------------------------------------------------------
classes = (
CUSTOM_OT_actions,
CUSTOM_OT_clearList,
CUSTOM_UL_items,
CUSTOM_objectCollection,
CUSTOM_OT_popup
)
def register():
from bpy.utils import register_class
for cls in classes:
register_class(cls)
# Custom scene properties
bpy.types.Scene.custom = CollectionProperty(type=CUSTOM_objectCollection)
bpy.types.Scene.custom_index = IntProperty()
def unregister():
from bpy.utils import unregister_class
for cls in reversed(classes):
unregister_class(cls)
del bpy.types.Scene.custom
del bpy.types.Scene.custom_index
if __name__ == "__main__":
register()
Note: the naming of the classes and variables might be off a little.