package ui import ( "cwtch.im/cwtch/model" "cwtch.im/ui/go/the" "encoding/hex" "fmt" "git.openprivacy.ca/openprivacy/log" "github.com/therecipe/qt/core" "reflect" ) type MessageModel struct { core.QAbstractTableModel ackIdx int handle string _ func(string) `signal:"setHandle,auto"` _ map[int]*core.QByteArray `property:"roles"` _ func() `constructor:"init"` _ func(int) *MessageWrapper `slot:"getMessage,auto"` _ func(int) `signal:"addMessage,auto"` _ func(int) `signal:"editMessage,auto"` _ func() `signal:"requestEIR,auto"` _ func(string) string `slot:"getNick,auto"` _ func(string) string `slot:"getImage,auto"` } type MessageWrapper struct { core.QObject model.Message _ int64 `property:"timestamp"` _ string `property:"peerID"` _ bool `property:"acknowledged"` _ string `property:"rawMessage"` _ string `property:"error"` _ string `property:"day"` _ string `property:"signature"` _ bool `property:"ackd"` } type OverlayJSONObject struct { Overlay int `json:"o"` Data string `json:"d"` } func (this *MessageModel) Handle() string { return this.handle } func (this *MessageModel) setHandle(handle string) { this.handle = handle } func (this *MessageModel) init() { sacrificialObject := NewMessageWrapper(nil) mdt := reflect.TypeOf(*sacrificialObject) roles := make(map[int]*core.QByteArray) for i := 0; i < mdt.NumField(); i++ { fieldName := mdt.Field(i).Name if fieldName == "_" { fieldName = mdt.Field(i).Tag.Get("property") } if fieldName == "acknowledged" { this.ackIdx = int(core.Qt__UserRole) + 1 + i } roles[int(core.Qt__UserRole)+1+i] = core.NewQByteArray2(fieldName, -1) } roles[int(core.Qt__DisplayRole)] = core.NewQByteArray2("display", -1) this.SetRoles(roles) this.ConnectData(this.data) this.ConnectRowCount(this.rowCount) this.ConnectColumnCount(this.columnCount) this.ConnectHeaderData(this.headerData) this.ConnectRoleNames(this.roleNames) } func (this *MessageModel) roleNames() map[int]*core.QByteArray { return this.Roles() } func (this *MessageModel) isGroup() bool { return len(this.Handle()) == 32 } func (this *MessageModel) getNick(handle string) string { return GetNick(handle) } func (this *MessageModel) getImage(handle string) string { return GetProfilePic(handle) } func (this *MessageModel) num() int { if this.Handle() == "" || the.Peer == nil { log.Debugf("MessageModel.num: early returning 0") return 0 } if this.isGroup() { group := the.Peer.GetGroup(this.Handle()) if group != nil { return len(group.Timeline.Messages) + len(group.UnacknowledgedMessages) } } else { contact := the.Peer.GetContact(this.Handle()) if contact != nil { return len(contact.Timeline.Messages) } } log.Warnf("MessageModel.num: group/contact was nil, returning 0") return 0 } // caveat emptor: accessing messages this way returns a MessageWrapper QObject whose properties have not yet been // initialized (but soon will be). accessing .Message parent class properties instead should work right away. func (this *MessageModel) getMessage(idx int) *MessageWrapper { modelmsg := model.Message{Message: "[an unexpected cwtch error occurred]"} var ackd bool if this.isGroup() { group := the.Peer.GetGroup(this.Handle()) if idx >= len(group.Timeline.Messages) { modelmsg = group.UnacknowledgedMessages[idx-len(group.Timeline.Messages)] } else { modelmsg = group.Timeline.Messages[idx] ackd = true } } else { if this.Handle() != "" && the.Peer != nil { contact := the.Peer.GetContact(this.Handle()) if contact != nil { if idx >= len(contact.Timeline.Messages) { log.Errorf("requested message[%d] of only %d", idx, len(contact.Timeline.Messages)) } else { modelmsg = contact.Timeline.Messages[idx] ackd = modelmsg.Acknowledged } } } } mw := NewMessageWrapper(nil) mw.Message = modelmsg mw.SetTimestamp(modelmsg.Timestamp.Unix()) mw.SetPeerID(modelmsg.PeerID) mw.SetError(modelmsg.Error) mw.SetAcknowledged(ackd) mw.SetAckd(ackd)//??why both?? mw.SetDay(modelmsg.Timestamp.Format("January 2, 2006")) mw.SetSignature(hex.EncodeToString(modelmsg.Signature)) mw.SetRawMessage(modelmsg.Message) return mw } func (this *MessageModel) data(index *core.QModelIndex, role int) *core.QVariant { if !index.IsValid() { return core.NewQVariant() } if index.Row() >= this.num() { return core.NewQVariant() } if role == int(core.Qt__DisplayRole) { role = index.Column() + int(core.Qt__UserRole) + 1 } if role == this.ackIdx { return core.NewQVariant1((*this.getMessage(index.Row())).Acknowledged) } return core.NewQVariant1(fmt.Sprintf("unimplemented role %s (%d)", this.roleNames()[role].ConstData(), role)) } func (this *MessageModel) headerData(section int, orientation core.Qt__Orientation, role int) *core.QVariant { if role != int(core.Qt__DisplayRole) || orientation == core.Qt__Vertical { return this.HeaderDataDefault(section, orientation, role) } mdt := reflect.TypeOf([]MessageWrapper{}).Elem() return core.NewQVariant12(mdt.Field(section).Name) } func (this *MessageModel) rowCount(parent *core.QModelIndex) int { return this.num() } func (this *MessageModel) columnCount(parent *core.QModelIndex) int { return reflect.TypeOf(MessageWrapper{}).NumField() } // perform this.BeginInsertRows() on the gui thread // important: // 1. idx MUST be set to this.num()'s value *before* calling addMessage() // 2. insert the message yourself // 3. this.RequestEIR() *must* be called afterward func (this *MessageModel) addMessage(idx int) { this.BeginInsertRows(core.NewQModelIndex(), idx, idx) } // perform this.EndInsertRows() on the gui thread after an AddMessage() func (this *MessageModel) requestEIR() { this.EndInsertRows() } // notify the gui that the message acknowledgement at index idx has been modified func (this *MessageModel) editMessage(idx int) { if idx < 0 || idx >= this.num() { log.Debugf("cant edit message %v. probably fine", idx) return } indexObject := this.Index(idx, 0, core.NewQModelIndex()) // replace third param with []int{} to update all attributes instead this.DataChanged(indexObject, indexObject, []int{this.ackIdx}) }