How to add a menu to a listView

Mac OSX specific forum
Wolfram
Enthusiast
Enthusiast
Posts: 567
Joined: Thu May 30, 2013 4:39 pm

How to add a menu to a listView

Post by Wolfram »

Hello,

does anybody know how to add a menu to a column of ListView?
To be able to fill the item by a choice from a menu.
macOS Catalina 10.15.7
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: How to add a menu to a listView

Post by Shardik »

Take a look into my code example. It detects row and column of a right click and opens a popup menu which allows you to choose an entry to be written into the right-clicked cell (tested successfully on MacOS 10.6.8 "Snow Leopard" and 10.12.1 "Sierra" with PB 5.43 x86 and x64 in both ASCII and Unicode mode):

Code: Select all

EnableExplicit

; ----- Only necessary for compilation with PB x64 on MacOS 10.12 "Sierra"
; Import "-stdlib=libc++ -mmacosx-version-min=10.7" : EndImport

Define CursorLocation.NSPoint
Define SelectedColumn.I
Define SelectedRow.I

OpenWindow(0, 200, 100, 250, 101, "Right click to select sex")
ListIconGadget(0, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 20,
  "Name", 110, #PB_ListIcon_GridLines)
AddGadgetColumn(0, 1, "Sex", GadgetWidth(0) - GetGadgetItemAttribute(0, 0,
  #PB_ListIcon_ColumnWidth) - 8)
AddGadgetItem(0, -1, "Harry Rannit")
AddGadgetItem(0, -1, "Ginger Brokeit")
AddGadgetItem(0, -1, "Didi Foundit")

If CreatePopupMenu(0)
  MenuItem(0, "Male")
  MenuItem(1, "Female")
EndIf

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_CloseWindow
      Break
    Case #PB_Event_Gadget
      If EventGadget() = 0 And EventType() = #PB_EventType_RightClick
        SelectedRow = GetGadgetState(0)
        CursorLocation\x = WindowMouseX(0)
        CursorLocation\y = WindowHeight(0) - WindowMouseY(0)
        CocoaMessage(@CursorLocation, GadgetID(0),
          "convertPoint:@", @CursorLocation, "fromView:", 0)
        SelectedColumn = CocoaMessage(0, GadgetID(0),
          "columnAtPoint:@", @CursorLocation)

        If SelectedColumn = 1
          DisplayPopupMenu(0, WindowID(0))
        EndIf
      EndIf
    Case #PB_Event_Menu
      Select EventMenu()
        Case 0
          SetGadgetItemText(0, SelectedRow, "Male", SelectedColumn)
        Case 1
          SetGadgetItemText(0, SelectedRow, "Female", SelectedColumn)
      EndSelect
  EndSelect
ForEver
Wolfram
Enthusiast
Enthusiast
Posts: 567
Joined: Thu May 30, 2013 4:39 pm

Re: How to add a menu to a listView

Post by Wolfram »

Thanks Shardik!

This helps.
But do you know how to build it like this in the picture.
Image
macOS Catalina 10.15.7
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: How to add a menu to a listView

Post by Shardik »

This new example code needs at least MacOS 10.7 (Lion). I tested it successfully on MacOS 10.8.6 (Mountain Lion) and 10.12.1 (Sierra) with PB 5.43 x86 and x64 in both ASCII and Unicode mode.

Since PureBasic is still using NSCells (deprecated cell-based approach, NSCells are not based on NSView, alot less flexible and therefore much harder to modify) to display the cell contents and according to Apple the cell-based and view-based approach may not be mixed, I had to use the delegate method tableView:viewForTableColumn:row: (introduced in Lion) and return a newly allocated view (NSComboBox for a column with a ComboBox or NSTextField for a normal column) for each cell that has to be displayed:

Code: Select all

EnableExplicit

; ----- Only necessary when compiling with PB x64 on MacOS 10.12 "Sierra"
; Import "-stdlib=libc++ -mmacosx-version-min=10.7" : EndImport

If OSVersion() < #PB_OS_MacOSX_10_7
  MessageRequester("Compilation terminated",
    "Sorry, but you need at least MacOS 10.7 (Lion) to compile this code!")
  End
EndIf

#ListIcon = 0

Define AppDelegate.I = CocoaMessage(0, CocoaMessage(0, 0,
  "NSApplication sharedApplication"), "delegate")
Define DelegateClass.I = CocoaMessage(0, AppDelegate, "class")
Define RowHeight.CGFloat = 25
Define Selector.I = sel_registerName_("tableView:viewForTableColumn:row:")

ProcedureC GetViewForCellCallback(Object.I, Selector.I, TableView.I,
  ColumnObject.I, Row.I)
  Protected CellContent.S
  Protected Column.I
  Protected ColumnID.S = PeekS(CocoaMessage(0, CocoaMessage(0,
    ColumnObject, "identifier"), "UTF8String"), -1, #PB_UTF8)
  Protected ComboBox.I
  Protected TextFrame.NSRect
  Protected View.I

  Column = Val(ColumnID)

  If Column = 1
    ; ----- If column = 1, create ComboBox and return it
    ComboBox = CocoaMessage(0, 0, "NSComboBox new")
    CocoaMessage(0, ComboBox, "addItemWithObjectValue:$",
      @"12 Parliament Way, Battle Street, By the Bay")
    CocoaMessage(0, ComboBox, "addItemWithObjectValue:$",
      @"130 PureBasic Road, BigTown, CodeCity")
    CocoaMessage(0, ComboBox, "addItemWithObjectValue:$",
      @"321 Logo Drive, Mouse House, Downtown")
    View = ComboBox
  Else
    ; ----- If Column <> 1, read current cell content, create new view with
    ;       NSTextField, write current content into that view and return it
    CellContent = GetGadgetItemText(#ListIcon, Row, Column)
    TextFrame\size\width = GetGadgetItemAttribute(#ListIcon,
      #PB_ListIcon_ColumnWidth, Column)
    TextFrame\size\height = 18
    View = CocoaMessage(0, CocoaMessage(0, 0, "NSTextField new"),
      "initWithFrame:@", TextFrame)
    CocoaMessage(0, View, "setStringValue:$", @CellContent)
  EndIf

  ProcedureReturn View
EndProcedure

OpenWindow(0, 200, 100, 430, 126, "ListIconGadget with ComboBox")
ListIconGadget(0, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 20,
  "Name", 110)
AddGadgetColumn(0, 1, "Address", GadgetWidth(0) - GetGadgetItemAttribute(0,
  0, #PB_ListIcon_ColumnWidth) - 8)
AddGadgetItem(0, -1, "Harry Rannit")
AddGadgetItem(0, -1, "Ginger Brokeit")
AddGadgetItem(0, -1, "Didi Foundit")

; ----- Increase row height to be able to display larger ComboBox
CocoaMessage(0, GadgetID(0), "setRowHeight:@", @RowHeight)

; ----- Initialize callback that returns a new NSView (view-based) instead
;       of the old NSCell (cell-based) for each cell drawn
class_addMethod_(DelegateClass, Selector, @GetViewForCellCallback(), "v@:@@@")
CocoaMessage(0, GadgetID(0), "setDelegate:", AppDelegate)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow
User avatar
fsw
Addict
Addict
Posts: 1572
Joined: Tue Apr 29, 2003 9:18 pm
Location: North by Northwest

Re: How to add a menu to a listView

Post by fsw »

Shardik wrote:Since PureBasic is still using NSCells (deprecated cell-based approach...
This is my experience as well: some OS X/macOS stuff is really outdated (examples: webgadget doesn't use WKWebview but the old webview, movie library still uses QuickTime instead of AVFoundation etc.)
Lets hope that Fred extends the next PureBasic version to be more up-to-date on macOS.

It's a bummer because there is no other easy to use programming language (that I know of) on macOS besides Swift 3.

On the flip-side I can see why it takes so long for the PureBasic team to update the PB-Libs:
Apple's macOS replacement libraries are not a 1 to 1 replacement. Lot's of stuff doesn't work the same.
In my experience it's a lot of trial and error because some lib-commands have been killed in the new library. And Apple's documentation is written in a way that it's assumed the reader is a well versed professional OS X programmer...
On top of that: XCode really sux big time, and the strange programming approach of half coding and the other half is disguised in some ui-event sub-menus where a "graphical connection" needs to be made in the interface builder...

I am to provide the public with beneficial shocks.
Alfred Hitshock
User avatar
Shardik
Addict
Addict
Posts: 1989
Joined: Thu Apr 21, 2005 2:38 pm
Location: Germany

Re: How to add a menu to a listView

Post by Shardik »

Although my code example posted above is working quite fine, it is a hack which messes around with PureBasic internals by returning view-based objects instead of cell-based objects. This prevents PureBasic events and PureBasic functions like GetGadgetItemText() to work correctly for the ListIconGadget! Therefore I programmed a more compatible cell-based approach using NSComboBoxCells. But now the ComboBoxes are not editable anymore. If you need editable ComboBoxes you should take a look into this more advanced code example.

This code example was tested successfully with PB 5.44 x86 and x64 in ASCII and Unicode mode on these MacOS versions:
- 10.6.8 (Snow Leopard) (it's only compilable with PB 5.44 x86; to compile it with x64 it's necessary to use PB 5.43 x64!)
- 10.8.6 (Mountain Lion)
- 10.9.5 (Mavericks)
- 10.12.1 (Sierra)

Code: Select all

EnableExplicit

#ListIcon = 0
#ComboBoxColumn = 1

Define AppDelegate.I = CocoaMessage(0,
  CocoaMessage(0, 0, "NSApplication sharedApplication"), "delegate")
Define ColumnObject.I
Define i.I
Define ComboBoxCell.I
Define DelegateClass.I = CocoaMessage(0, AppDelegate, "class")
Define NotificationCenter.I = CocoaMessage(0, 0,
  "NSNotificationCenter defaultCenter")
Define RowHeight.CGFloat = 21
Define Selector.I = sel_registerName_("itemChanged:")
Define SubclassedComboBoxCell.I

ProcedureC ComboBoxSelectionChangedCallback(Object.I, Selector.I,
  Notification.I)
  Protected Cell.I
  Protected ItemIndex.I
  Protected ItemText.S
  Protected Row.I

  Cell = CocoaMessage(0, GadgetID(#ListIcon), "selectedCell")

  If Cell
    Row = CocoaMessage(0, GadgetID(#ListIcon), "selectedRow")
    ItemIndex = CocoaMessage(0, Cell, "indexOfSelectedItem")

    If ItemIndex >= 0
      ItemText = PeekS(CocoaMessage(0, CocoaMessage(0,
        Cell, "itemObjectValueAtIndex:", ItemIndex),
        "UTF8String"), -1, #PB_UTF8)

      If ItemText <> GetGadgetItemText(#ListIcon, Row, #ComboBoxColumn)
        SetGadgetItemText(#ListIcon, Row, ItemText, #ComboBoxColumn)
      EndIf
    EndIf
  EndIf
EndProcedure 

OpenWindow(0, 200, 100, 250, 113, "ListIcon with ComboBoxes")
ListIconGadget(#ListIcon, 10, 10, WindowWidth(0) - 20, WindowHeight(0) - 20,
  "Friend", 70, #PB_ListIcon_GridLines)
AddGadgetColumn(#ListIcon, #ComboBoxColumn, "Country",
  GadgetWidth(#ListIcon) - GetGadgetItemAttribute(#ListIcon, 0,
  #PB_ListIcon_ColumnWidth) - 8)
AddGadgetItem(#ListIcon, -1, "Alice")
AddGadgetItem(#ListIcon, -1, "Bob")
AddGadgetItem(#ListIcon, -1, "Eve")

; ----- Increase row height to be able to display the larger ComboBox
CocoaMessage(0, GadgetID(#ListIcon), "setRowHeight:@", @RowHeight)

; ----- Disable highlighting of selected row
CocoaMessage(0, GadgetID(#ListIcon), "setSelectionHighlightStyle:", -1)

; ----- Center text in 1st column vertically
;       Caution: _setVerticallyCentered: is an internal flag; it could simply
;       disappear without warning in a future release and its use may lead
;       to a rejection in Apple's AppStore!
ColumnObject = CocoaMessage(0, CocoaMessage(0,
  GadgetID(#ListIcon), "tableColumns"), "objectAtIndex:", 0)
CocoaMessage(0, CocoaMessage(0, ColumnObject, "dataCell"),
  "_setVerticallyCentered:", #YES)

; ----- Create a NSComboBoxCell (used internally as an editable ComboBox by
;       PureBasic) and use it in all cells of column #ComboBoxColumn
ColumnObject = CocoaMessage(0, CocoaMessage(0,
   GadgetID(#ListIcon), "tableColumns"), "objectAtIndex:", #ComboBoxColumn)
ComboBoxCell = CocoaMessage(0, 0, "NSComboBoxCell new")
CocoaMessage(0, ColumnObject, "setDataCell:", ComboBoxCell)

; ----- Remove border around button (special style for NSComboBoxCell in
;       NSTableView)
CocoaMessage(0, ComboBoxCell, "setButtonBordered:", #NO)

; ----- Fill ComboBox
CocoaMessage(0, ComboBoxCell, "addItemWithObjectValue:$", @"France")
CocoaMessage(0, ComboBoxCell, "addItemWithObjectValue:$", @"Germany")
CocoaMessage(0, ComboBoxCell, "addItemWithObjectValue:$", @"USA")

; ----- Subclass ComboBoxCell
SubclassedComboBoxCell = objc_allocateClassPair_(CocoaMessage(0,
  ComboBoxCell, "class"), "SubclassedComboBoxCell", 0)
objc_registerClassPair_(SubclassedComboBoxCell)
object_setClass_(ComboBoxCell, SubclassedComboBoxCell)

; ----- Set callback to detect selection change in ComboBox
class_addMethod_(DelegateClass, Selector, @ComboBoxSelectionChangedCallback(),
  "v@:@")
CocoaMessage(0, NotificationCenter,
  "addObserver:", AppDelegate,
  "selector:", Selector,
  "name:$", @"NSTableViewSelectionDidChangeNotification",
  "object:", CocoaMessage(0, GadgetID(#ListIcon), "menu"))

SetActiveGadget(#ListIcon)

Repeat
Until WaitWindowEvent() = #PB_Event_CloseWindow

; ----- Display countries currently selected in ComboBoxes
For i = 0 To 2
  Debug Str(i + 1) + ". " + GetGadgetItemText(#ListIcon, i, 0) + " - " +
    GetGadgetItemText(#ListIcon, i, #ComboBoxColumn)
Next i

CocoaMessage(0, NotificationCenter, "removeObserver:", AppDelegate)
Post Reply