HeroEngine Forums
Welcome, Guest. Please login or Register for HeroCloud Account.

Author Topic: Making Spec Editor Cells for Lists  (Read 3893 times)

FI-ScottZ

  • General Accounts
  • *
  • Posts: 1407
    • View Profile
    • Forever Interactive, Inc.
Making Spec Editor Cells for Lists
« on: Sep 05, 13, 04:02:13 PM »

I guess this is not so much a tutorial, but some tips for making a gui that were requested of me and I figure I may as well share them with everyone.



The standard spec editor cells available do not natively support list fields (at least not very well and with no interaction), therefore you may well want to create a custom editor cell for list fields.

For one, you need to be pretty familiar with gui editing and how to work with standard controls such as text boxes, check boxes, buttons, and drop-down menus, specifically how to do both input and output with them.

What you would do is make a gui control with a panel as the parent control and have its class be a custom gui control class which is at least derived from _GUINodePropertyEditorCell, or possibly from a child class of that if you are basing it on a particular existing cell that uses a different class.

Use the gui editor to add appropriate controls to your cell based on your needs, and write the class script with at least the methods _setCollectionCellValue(), _setCollectionCellValueFromNodeRefByField(), _updateNodeFieldWithCellValue(), and _getCollectionCellValue().  Those first two are for getting values from the spec into the cell for  display while the others do the opposite, getting the value from the display into the spec.

So, for a list field, what we did was create a cell that has a drop-down menu for displaying the values in the list, and 3 buttons:  Add, Change, and Delete.  How you get the values into the drop-down depends on how the user will interact with the cell.  If they will only type stuff in, you would have a text box as well.  If the user clicks Add, the contents of the text box is added to the drop-down, while the Change and Delete buttons both deal with the value currently selected.  In some of ours, we have the Add and Change buttons open a dialog for selection by the user.  Specifically, one uses the $GUI.LaunchFileDialogForControl() method for choosing a file from the repository, and another uses the public function _createSpecSelector() of the _GUISpecSelector class to allow selection of a spec from another oracle.  That last one is very handy for having specs reference specs in other oracles.

When you read or write the cell, you deal with the contents of that drop-down since that represents the list contents.

There are many other ways it could be done.  What it boils down to is that you need a cell that can represent the contents of a list and some controls that allow the user to manipulate that list, as well as the back-end code to read and write the cell.

Finally, you need to specify that your cell be used for a particular field.  That happens in the shared function _createNodePropertyEditorCellForField() of the client-side spec class.  For instance, for an integer field if you want to use a numeric up-down instead of the standard text box for a field called "activationCost"

Code: [Select]
shared function _createNodePropertyEditorCellForField( fieldName as String ) as NodeRef of Class _GUINodePropertyEditorCell
  editCell as NodeRef of Class _GUINodePropertyEditorCell
  when tolower( fieldname )
    is "activationcost"
      editCell = CreateNodeFromPrototype( "_NodePropertyEditorCellNumericField" )
      editCell.build = true
    .
  .
  return editCell
.

In the code after building it, you might perform any number of customizations to the editCell as needed.

And for numeric fields, you may also want to use the shared function _nodePropertyEditorCellNumericFieldRange() in a spec to specify the range for a given field.



Once you are very familiar with creating interactive guis and how to make custom cells, you can let your imagination run wild on the types of interfaces you can add to spec editors to meet your specific needs.
Logged
Scott Zarnke
Lead Programmer, Visions of Zosimos
CTO, Forever Interactive, Inc.

Tarra2012

  • General Accounts
  • *
  • Posts: 113
    • View Profile
Re: Making Spec Editor Cells for Lists
« Reply #1 on: Sep 15, 13, 11:03:10 AM »

Tutorial for Creating a Spec-Editor-Cell that is able to manage a "LIST" attribute.
The goal is to add/remove "IDS" to a "List of IDS" in a Spec.

Due to several benefits the target attribute "custom_strlist" as "list of string"
is used to store the ID information. On Top we will add order manipulating buttons.
The list elements can be moved up and down.


What possible use cases?
In our World we created a relationship 1 QuestSpec can have 1-to-many TaskSpec.
With the example we can Add/Remove TasksSpecs to a QuestSpec and manipulate their order in the list.


Prerequisit
You Created a Spec "YourSpec" with an attribute "custom_strlist" as "list of string"
"YourSpecClassMethods" Clientside is existing and you can call the Standard Spec Editor without errors.


Clientside

1.  Create GUI Prototype
Create (via DOM) "YourCellClass" with parent class "_GUINodePropertyEditorCellList"   
Create (via GUI Editor) "YourGUIPrototype" with parent class "YourCellClass" (dont inherit!)

2. Add Buttons and Elements to Prototype
a)
Open "YourGuiPrototype" via GUIEditor
Click on the Top Root Element of the GUI (Property Editor)
Search in "GUI XML Organizer" the _SortableCollection
doubleclick the _sortableCollection
Now new elements below "_sortableCollection" should be inside "Properties" GUI Tree

b)
rename "_sortableCollection" to "sortableCollection"
Click on the Element "sortableCollection" Element of the GUI (Property Editor)
search in "GUI XML Organizer" the _panel
doubleclick the _panel
Now a new _panel should be below "sortableCollection" inside "Properties" Gui Tree

c)
Click on the Element "_panel" Element of the GUI (Property Editor)
Search in "GUI XML Organizer" the _button
doubleclick the _button 4x times
Now 4 buttons should be "children" Elements of the "_panel" in GUI Tree
Rename the buttons
 add_id
 remove_id
 up_id
 down_id


d)
make sure that the sortableCollection Elements are visible
make sure your panel and the buttons are visible
might need dragging borders in visible gui area
and arranging position attributes

SAVE your Prototype!



3. Edit YourSpecClassMethods Script

We will use the SpecSelector "_PropSpecOracle".
This will pop up a gui, where you can select one PropSpec as an "ID" for your list.
Of course you will need to change it to one of Your Specs later. But this is changing only one line of code.

Paste the Following Code inside the ClassMethods

Code: [Select]
shared function _createNodePropertyEditorCellForField( fieldName as String ) as NodeRef of Class

_GUINodePropertyEditorCell
  editCell as NodeRef of Class _GUINodePropertyEditorCell
 
  when tolower( fieldName )
    is "custom_strlist"
      editCell = CreateNodeFromPrototype("YourGUIPrototype")
      editCell._setNodePropertyEditorCellFieldName("custom_strlist")
      editCell.build = true
    .
  .
  return editCell
.



4. Edit YourCellClassMethods Script (DOM EDitor open script, create new empty one)

Paste the following Code

Code: [Select]

method _getCollectionCellHeight() as Float
 return 220.0
.

//Adds a new Row to the GUI Sortable Collection
method addID(newid as ID,newname as String)
 
  //Setup noderefs to GUI und the EditNode
  sortable as NodeRef of Class GUIControl = FindGUIControlByName( me, "sortableCollection" )
  var fieldType = me._getNodePropertyEditorCellLocalFieldType()
  var theNode = me._getNodePropertyEditorEditNode()
  var rows = sortable._getAllCollectionRows()
  nextindex as Integer = rows.length + 1
 
  //Create a new Cell for Sortable Collection
  valueCell as NodeRef of Class _GUISortableCollectionCell = CreateNodeFromPrototype(

"_nodePropertyEditorCellTextInputBox" )
  valueCell.build = true
  valueCell.tooltip = "Cell Index " + nextindex
  //valueCell._setNodePropertyEditorCellIDValue( newid )
  valueCell._setNodePropertyEditorCellStringValue(newid+":"+newname)
 
  guidControls as List of NodeRef of Class _GUISortableCollectionCell
  add back valueCell to guidcontrols
  sortable._addCollectionRowControls( guidControls ) 
 
.


//Button Functions - not Clean
method _onButtonMouseClick(button as NodeRef of Class _GUIButton, args references Class GUIMouseEvent)
  sortable as NodeRef of Class GUIControl = FindGUIControlByName( me, "sortableCollection" )
  when button.name
    is "add_id"
      args.handled = true
      var listener = $SPECORACLEUTILS.createScriptMultiListener(SYSTEM.EXEC.THISSCRIPT, false)
      var gui = _GUISpecSelectorClassMethods:_createSpecSelector( listener )
      gui._SetSpecSelectorType( "_PropSpecOracle" )
      gui._setSpecSelectorParent(me)
     
    .
    is "remove_id"
      args.handled = true
      sortable._removeSelectedCollectionRows()
    .
   
    is "down_id"
     found as Boolean
     //tmp Vars for saving Rows that need to shift
     tmpdel as List of NodeRef of Class _GUISortableCollectionRow
     tmpdelint as List of Integer
     selectedRow as NodeRef of Class _GUISortableCollectionRow
     selectedint as Integer
     
     //Now iterate through all rows and save/mark all after "selected row" in the tmp vars
     var clientarea = sortable.getClientarea()
     assert(clientarea != 0, "Couldn't find the content area!")
     loop i from 1 to clientarea.children.length
       crow as NodeRef of Class _GUISortableCollectionRow = clientarea.children[i]
       if found == true
         add back crow to tmpdel
         add back i to tmpdelint
       .
       if crow.selected == true
         found = true
         selectedRow = crow
         selectedint = i
       .
     .
     if selectedint >= clientarea.children.length
       args.handled = true
       return
     . 
     
      removecount as Integer= 1+tmpdel.length
      remove clientarea.children at selectedint count removecount keep
   
     //Add row below selected to GUI
      if tmpdel.length > 0
       add back tmpdel[1] to clientarea.children
      .
     //Add selected row  to GUI
      add back selectedRow to clientarea.children
   
     //Add rest in tmplist to GUI
      if tmpdel.length > 0
      foreach row in tmpdel
        if tmpdel[1] <> row
        add back row to clientarea.children
        .
      .
      .
 
    .
     is "up_id"
     found as Boolean
     //tmp Vars for saving Rows that need to shift
     tmpdel as List of NodeRef of Class _GUISortableCollectionRow
     tmpdelint as List of Integer
     selectedRow as NodeRef of Class _GUISortableCollectionRow
     selectedint as Integer
     
     //Now iterate through all rows and save/mark all after "selected row" in the tmp vars
     var clientarea = sortable.getClientarea()
     assert(clientarea != 0, "Couldn't find the content area!")
     loop i from 1 to clientarea.children.length
       crow as NodeRef of Class _GUISortableCollectionRow = clientarea.children[i]
       if found == true
         add back crow to tmpdel
         add back i to tmpdelint
       .
       if crow.selected == true
         found = true
         selectedRow = crow
         selectedint = i
       .
     .
     if selectedint < 2
       args.handled = true
       return
     . 
     //add front the row before the seletced row
     add front clientarea.children[selectedint-1] to tmpdel
     add front selectedint-1 to tmpdelint
     
      removecount as Integer= 1+tmpdel.length
      removeoff as Integer = selectedint-1
      remove clientarea.children at removeoff count removecount keep
   
      //Add selected row  to GUI
      add back selectedRow to clientarea.children
     
     //Add row ahead of selected to GUI
      if tmpdel.length > 0
       add back tmpdel[1] to clientarea.children
      .
     
     //Add rest in tmplist to GUI
      if tmpdel.length > 0
      foreach row in tmpdel
        if tmpdel[1] <> row
        add back row to clientarea.children
        .
      .
      .
     
    .     
    default
      println("default")
    .
  .
 
  args.handled = true
.


//Reads the Spec - String Unmarshal - Fills the GUI Cell (inside the PropertyEditor)
method _setCollectionCellValueFromNodeRefByField( fieldName as String )
 
  me._GUINodePropertyEditorCellFieldName = fieldName
  theNode as NodeRef = me._getNodePropertyEditorEditNode()
 
  localfieldName as String = fieldName
 
  while ( findString( localFieldName, ".") > 0 )
    periodLoc as Integer = findString( localFieldName, ".")
    // has a subfield so we need to parse it to get there
    localFieldName = subString( localFieldName, periodLoc + 1, fieldName.length - periodLoc )
  .
 
  var fieldType = GetFieldType( localfieldName )
 
  sortableCollection as NodeRef of Class _GUISortableCollection = FindGUIControlByName( me, "sortableCollection"

)
  sortableCollection._setNodePropertyEditorSortableCollectionShowButtons( false )

  columnHeaders as List of String
  add back "Value" to columnHeaders
  sortableCollection._setCollectionColumnHeaders( columnHeaders )
 
  index as Integer
  when tolower( fieldType )
    is "list of string"
      strings as List of String = theNode.fieldCollection[fieldName]
      foreach s in strings
        index = index + 1
       
        valueCell as NodeRef of Class _GUISortableCollectionCell = CreateNodeFromPrototype(

"_nodePropertyEditorCellTextInputBox" )
        valueCell.build = true
        valueCell.tooltip = "Cell Index " + index
        valueCell._setNodePropertyEditorCellStringValue( s )
       
        guidControls as List of NodeRef of Class _GUISortableCollectionCell

        add back valueCell to guidcontrols
       
        sortableCollection._addCollectionRowControls( guidControls )
      .
    .
    is "list of integer"
      integers as List of Integer = theNode.fieldCollection[fieldName]
      foreach i in integers
        index = index + 1
       
        valueCell as NodeRef of Class _GUISortableCollectionCell = CreateNodeFromPrototype(

"_nodePropertyEditorCellNumericInputBox" )
        valueCell.build = true
        valueCell._setNodePropertyEditorCellForceInteger( true )
        valueCell.tooltip = "Cell Index " + index
        valueCell._setNodePropertyEditorCellIntegerValue( i )
       
        guidControls as List of NodeRef of Class _GUISortableCollectionCell
       
        add back valueCell to guidcontrols
       
        sortableCollection._addCollectionRowControls( guidControls )
      .
    .
    is "list of float"
      floats as List of Float = theNode.fieldCollection[fieldName]
      foreach f in floats
        index = index + 1

        valueCell as NodeRef of Class _GUISortableCollectionCell = CreateNodeFromPrototype(

"_nodePropertyEditorCellNumericInputBox" )
        valueCell.build = true
        valueCell.tooltip = "Cell Index " + index
        valueCell._setNodePropertyEditorCellForceInteger( false )
        valueCell._setNodePropertyEditorCellFloatValue( f )
       
        guidControls as List of NodeRef of Class _GUISortableCollectionCell
       
        add back valueCell to guidcontrols
       
        sortableCollection._addCollectionRowControls( guidControls )
      .
    .
    is "list of boolean"
      bools as List of Boolean = theNode.fieldCollection[fieldName]
      foreach b in bools
        index = index + 1
       
        valueCell as NodeRef of Class _GUISortableCollectionCell = CreateNodeFromPrototype(

"_nodePropertyEditorCellNumericInputBox" )
        valueCell.build = true
        valueCell.tooltip = "Cell Index " + index
        valueCell._setNodePropertyEditorCellBooleanValue( b )
       
        guidControls as List of NodeRef of Class _GUISortableCollectionCell
       
        add back valueCell to guidcontrols
       
        sortableCollection._addCollectionRowControls( guidControls )
      .
    .
    default
      if findString( tolower( FieldType ), "list of noderef of class" ) = 1
        noderefs as List of NodeRef = theNode.fieldCollection[fieldName]
        foreach n in noderefs
          index = index + 1
 
          valueCell as NodeRef of Class _GUISortableCollectionCell = CreateNodeFromPrototype(

"_nodePropertyEditorCellIdField" )
          valueCell.build = true
          valueCell.tooltip = "Cell Index " + index
          valueCell._setNodePropertyEditorCellIDValue( n )
         
          guidControls as List of NodeRef of Class _GUISortableCollectionCell
         
          add back valueCell to guidcontrols
         
          sortableCollection._addCollectionRowControls( guidControls )
        .
      .
    .
  .
.

//Update the Values fomr GUI -> NODE - >SPEC
method _updateNodeFieldWithCellValue( )

  sortable as NodeRef of Class GUIControl = FindGUIControlByName( me, "sortableCollection" )
 
  var fieldType = me._getNodePropertyEditorCellLocalFieldType()
 
  if not me._getNodePropertyEditorCellReadOnly()
    var theNode = me._getNodePropertyEditorEditNode()
   
    var rows = sortable._getAllCollectionRows()
   
    when tolower( fieldtype )
      default
        strings as List of String
        foreach r in rows
          var cells = r._getCollectionRowCells()
          add back cells[1]._getCollectionCellValue() to strings
        .
       
        // this is a little bit wierd, but it is using the underlying capabilities of C to handle
        //   varient data in marshalling to get around the strongly typed nature of HSL
        //
        s as String
        marshal strings to s
       
        unmarshal theNode.fieldCollection[ me._GUINodePropertyEditorCellFieldName ] from s
      .
    .
  .
.

//Call Back from SpecSelector
shared function EventRaisedNotify(obsSubject as NodeRef of Class ObsSubject, obsListener as NodeRef of Class

ObsListener, event as NodeRef)
  #if debug
    println("EventRaisedNotify()")
  #endif
  where event is kindof _extendedEventObject
    #if debug
     println("EventRaisedNotify!! subject: " + obsSubject + " listener: " + obsListener + " event: " + event + "

type: " + event.eventType)
    #endif
    when event.eventType
      is "SPECSSELECTED"
        chosenSpecID as ID
        fxName as String
        fxDesc as String
       
        selectedSpecs as List of ID
        unmarshal selectedSpecs from event._extendedEventLookup["selectedSpecs"]
        if selectedSpecs.length > 0
          chosenSpecID = selectedSpecs[1]
         
          headers as List of String
          unmarshal headers from event._extendedEventLookup[chosenSpecID]
   
          where ObsSubject is kindof _GUISpecSelector
            newnode as NodeRef of Class EO_QuestSpec_TaskID_Editor = ObsSubject._getSpecSelectorParent()
            //AddID to GUI need to pass ID AND Name otherwise we wouldnt see name in List
            newnode.addID(chosenSpecID,headers[2])
          .
      .
    .
    .
  .
.



5. Open "YourSpec" Standard Spec Editor.
The "custom_strlist" cell should be visible with the buttons (add/remove/up/down)

Click 3x add Button and select a Spec of the List (you can even choose same spec, in
our example case you can choose from PropSpec.

Now test if remove/up/down Buttons are working.
Be sure one Cell in the list is highlighted.
Otherwise remove/up/Down cant work.


6. Some short explanations
The "list of string" enables the view of not only the ID but also a Name.
This is much more comfortable for "Editor" Users.

Isnt the use of "list of string" a waste of space, if only a "list of ID" is needed?
Yes and no. In my journey through understanding specs i saw that every spec data is marshalled and saved as a string. (No matter if its vector,boolean,string,id,noderef). You can see parts of this in the code above.

In this example i used a ":" delimiter in the saved string to distinguish two informations.
The ID, a Name.


What were you thinking with the "up"/"Down" Functions?
Sometimes the position of elements in a list can be meaningful.
The sortabeCollection MIGHT hold other premade functions that manipulate the INDEX order.
In this tutorial" i coded it manually by changing the "clientarea" children. It looks a bit strange. I would

recommend to search other options too.


I cant call my "Standard Spec Editor"?
Use the command if you dont know how to add a LINK to the Hotspot Menu
Paste the Line in the Chat Window (Heroblade Editor)

/heoracle open "YourSpecOracle"


Good luck and enjoy your Oracle List Editor. This is my first tutorial and i hope there are no parts missing. Am happy for any feedback.

Tarra
« Last Edit: Sep 20, 13, 03:23:13 PM by Tarra2012 »
Logged

Tarra2012

  • General Accounts
  • *
  • Posts: 113
    • View Profile
Re: Making Spec Editor Cells for Lists
« Reply #2 on: Sep 20, 13, 02:33:29 PM »

A picture of how the editor looks like with the list attribute:

http://imageshack.us/photo/my-images/694/3boi.png/


Logged

Maldris

  • General Accounts
  • *
  • Posts: 49
    • View Profile
Re: Making Spec Editor Cells for Lists
« Reply #3 on: Jul 04, 14, 08:04:08 AM »

First off my thanks to Tarra2012, I based my solution heavily on the code you provided and it helped imensely, thank you.

However I have a few notes for anyone using this now, based on things I found necessary.
There is also one usability issue I find myself having that someone may be able to advise me on (bellow)

I noticed that the system was auto-sorting my simple lists when it loaded them, despite println tests showing that the list was saved and actually loaded in the original order. as I wanted my lists to be able to not be in alphabetical order this was a problem.
to fix this, during the _setCollectionCellValurFromNodeRefByField( fieldName as String ) call add the following:

Code: [Select]
sortableCollection._setSortableCollectionIsSortable(false)

The issue I've been having is that I cant consistently select the row to use the sorting and delete buttons using the mouse. This can be overcome by clicking into the cells and using the arrow keys (with some fiddling around). normally I just end up clicking into the editor cell instead.
While I can just use this keyboard method, a mouse based method would be easier for being able to go back and fine tune elements order, could anyone advise me as to what I am doing wrong?

anyway, thank you once again to Tarra2012 for the basis, and I hope the mentioned optional fix and keyboard workaround is useful to someone.
Logged

Tarra2012

  • General Accounts
  • *
  • Posts: 113
    • View Profile
Re: Making Spec Editor Cells for Lists
« Reply #4 on: Sep 24, 14, 04:11:08 PM »

I cant help you on this usability issue, but am happy to see, that someone tried those "editor cells for lists".

Maybe you can post a graphic link of your solution, my old Links seem not to work anymore.

I couldnt see the observation, that the list sorts itself, but i guess everyone who sets it up, could run into the same behaviour.
Logged

Maldris

  • General Accounts
  • *
  • Posts: 49
    • View Profile
Re: Making Spec Editor Cells for Lists
« Reply #5 on: Oct 08, 14, 08:09:47 AM »

ok some images taken of mine to help reference, this is the basic list editor implementation I made
https://www.dropbox.com/s/ad2x3c9pzirmjou/list%20editor.png?dl=0

I also made a derivative on this for specificity editing lookup lists, this was to speed up some particular edits that were slightly better served, and easier to manage the code side of editing in this way.
https://www.dropbox.com/s/ad2x3c9pzirmjou/list%20editor.png?dl=0

hope that helps people.
Logged

Maldris

  • General Accounts
  • *
  • Posts: 49
    • View Profile
Re: Making Spec Editor Cells for Lists
« Reply #6 on: Oct 08, 14, 08:12:28 AM »

more generally I'm presently trying to expand it to be able to edit a list of class without having to write custom code for each class (a pain in the ass, but its how were handling the small number of class lists we have so far), but that's still a work in progress, Ill let people know how I go, though advice would be appreciated, I repeatedly find myself banging my head on a brick wall with this one.
Logged

Jrome90

  • General Accounts
  • *
  • Posts: 330
    • View Profile
Re: Making Spec Editor Cells for Lists
« Reply #7 on: Oct 08, 14, 05:41:20 PM »

more generally I'm presently trying to expand it to be able to edit a list of class without having to write custom code for each class (a pain in the ass, but its how were handling the small number of class lists we have so far), but that's still a work in progress, Ill let people know how I go, though advice would be appreciated, I repeatedly find myself banging my head on a brick wall with this one.
What is the reason for needing custom code for each class?
Logged

Maldris

  • General Accounts
  • *
  • Posts: 49
    • View Profile
Re: Making Spec Editor Cells for Lists
« Reply #8 on: Oct 08, 14, 05:49:48 PM »

the system doesn't natively edit list of classes very well, it either expresses the class as a marshalled string (best case scenario) or it just doesn't display any elements.

I've had 3-4 cases of needing lists of classes and so far its been faster to manually make the change, but I want to come up with a better system to avoid this.

there was also talk of an approach to part of our ability system (this method has thankfully been decided against in favour of another method) of having some of the properties stored in a list of class with child classes of varying field number, the thought of managing that made me very much want an automatic system for it. Though as I said, were not using that concept any more (thankfully) so its not as pressing.
Logged

Jrome90

  • General Accounts
  • *
  • Posts: 330
    • View Profile
Re: Making Spec Editor Cells for Lists
« Reply #9 on: Oct 08, 14, 08:37:20 PM »

the system doesn't natively edit list of classes very well, it either expresses the class as a marshalled string (best case scenario) or it just doesn't display any elements.

I've had 3-4 cases of needing lists of classes and so far its been faster to manually make the change, but I want to come up with a better system to avoid this.

there was also talk of an approach to part of our ability system (this method has thankfully been decided against in favour of another method) of having some of the properties stored in a list of class with child classes of varying field number, the thought of managing that made me very much want an automatic system for it. Though as I said, were not using that concept any more (thankfully) so its not as pressing.

You could get the fields of a class, create the "Edit fields" for each field in the class.
Where each class element in the list has a seperate "container" of edit fields.

When you save the list data you can do the following:

1)iterate each container get the data inside
2)then put the data into a class variable
3)lastly insert the class  variable data into the list

You can do something similar to populate the "edit fields" as well.

*In theory this could work. But I'm not 100% sure
Logged