github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/client/GUI/common/documents.go (about) 1 package common 2 3 import ( 4 "errors" 5 "fmt" 6 "path/filepath" 7 "strings" 8 "sync" 9 10 dm "github.com/benoitkugler/goACVE/server/core/datamodel" 11 rd "github.com/benoitkugler/goACVE/server/core/rawdata" 12 13 "github.com/therecipe/qt/core" 14 15 "github.com/therecipe/qt/gui" 16 17 "github.com/benoitkugler/goACVE/client/GUI/lists" 18 19 "github.com/benoitkugler/goACVE/client/GUI/basic" 20 21 "github.com/benoitkugler/goACVE/client/controllers" 22 23 "github.com/therecipe/qt/widgets" 24 ) 25 26 type sharedMiniatures struct { 27 sync.RWMutex 28 d map[int64][]byte 29 } 30 31 func newSharedMiniatures() *sharedMiniatures { 32 var s sharedMiniatures 33 s.d = make(map[int64][]byte) 34 return &s 35 } 36 37 func (s *sharedMiniatures) Keys() map[int64]bool { 38 s.RLock() 39 defer s.RUnlock() 40 out := make(map[int64]bool, len(s.d)) 41 for i := range s.d { 42 out[i] = true 43 } 44 return out 45 } 46 47 func (s *sharedMiniatures) Read(i int64) []byte { 48 s.RLock() 49 defer s.RUnlock() 50 b := s.d[i] 51 return b 52 } 53 54 func (s *sharedMiniatures) WriteMany(min map[int64][]byte) { 55 s.Lock() 56 defer s.Unlock() 57 for id, m := range min { 58 s.d[id] = m 59 } 60 } 61 62 type Documents struct { 63 *widgets.QDialog 64 forAide bool 65 66 main *controllers.MainController 67 editable bool 68 miniatures *sharedMiniatures 69 documents lists.Table 70 preview *widgets.QLabel 71 72 refresh *widgets.QToolButton 73 upload, download *widgets.QPushButton 74 } 75 76 func NewDocuments(main *controllers.MainController, editable bool, forAide bool) *Documents { 77 d := Documents{QDialog: basic.Dialog2("Documents"), main: main, 78 editable: editable, forAide: forAide} 79 d.miniatures = newSharedMiniatures() 80 d.preview = basic.Label("<i>Aucune miniature.</i>") 81 d.preview.SetMinimumWidth(200) 82 d.preview.SetAlignment(core.Qt__AlignCenter) 83 84 d.refresh = widgets.NewQToolButton(nil) 85 d.refresh.SetIcon(basic.Icons.Refresh) 86 d.refresh.SetToolTip("Rafraichir la miniature") 87 d.refresh.ConnectClicked(func(_ bool) { 88 if item, ok := d.documents.CurrentData(); ok { 89 d.refreshMiniature(item.Id.Int64()) 90 } 91 }) 92 d.refresh.SetObjectName(basic.ONAction) 93 94 d.upload = basic.Button("Remplacer") 95 d.upload.ConnectClicked(d.remplaceDocument) 96 d.upload.SetObjectName(basic.ONAction) 97 98 d.download = basic.Button("Télécharger") 99 d.download.ConnectClicked(d.downloadDocument) 100 d.download.SetObjectName(basic.ONAction) 101 102 d.refresh.SetEnabled(false) 103 d.upload.SetEnabled(false) 104 d.download.SetEnabled(false) 105 106 newDoc := basic.Button("Ajouter") 107 newDoc.SetObjectName(basic.ONAdd) 108 newDoc.ConnectClicked(d.ajouteDocument) 109 rightHeader := widgets.NewQHBoxLayout() 110 rightHeader.AddWidget(newDoc, 1, core.Qt__AlignRight) 111 d.documents = lists.Table{ 112 Liste: lists.Liste{ 113 Headers: []rd.Header{ 114 {Field: dm.DocumentNomPrenom, Label: "Lié à"}, 115 {Field: dm.DocumentCategorie, Label: "Catégorie"}, 116 {Field: dm.DocumentNomClient, Label: "Fichier"}, 117 {Field: dm.DocumentTaille, Label: "Taille"}, 118 {Field: dm.DocumentDescription, Label: "Description"}, 119 {Field: dm.DocumentDateHeureModif, Label: "Modifié le"}, 120 }, 121 Placeholder: "Aucun document.", 122 MinHeight: 400, 123 MinWidth: 950, 124 OnSearch: func(pattern string) { 125 list := main.Base.GetAllDocuments(forAide) 126 if pattern != "" { 127 list = dm.RechercheDetaillee(list, pattern, d.documents.Liste.Headers) 128 } 129 d.documents.Model().SetData(list) 130 d.completeMiniatures() 131 }, 132 OnSort: func(field rd.Field, reverse bool) { 133 data := d.documents.Model().GetData() 134 dm.SortBy(data, field, reverse) 135 d.documents.Model().SetData(data) 136 }, 137 Title: "Documents", 138 RightHeader: rightHeader, 139 }, 140 } 141 if editable { 142 d.documents.OnDelete = func(acces rd.Item, _ int) { 143 d.supprimeDocument(acces.Id.Int64()) 144 } 145 } 146 d.documents.OnClick = func(acces rd.Item, _ int) { 147 d.activateItem(acces.Id) 148 } 149 d.documents.Init() 150 d.documents.HorizontalHeader().SetMinimumSectionSize(200) 151 152 d.documents.ConnectDataChanged(func() { 153 d.refresh.SetEnabled(false) 154 d.upload.SetEnabled(false) 155 d.download.SetEnabled(false) 156 d.preview.SetText("<i>Aucune miniature.</i>") 157 }) 158 159 lay := widgets.NewQGridLayout(d) 160 lay.AddWidget3(d.documents, 0, 0, 3, 1, 0) 161 lay.AddWidget2(basic.Label("Aperçu"), 0, 1, 0) 162 lay.AddWidget2(d.refresh, 0, 2, core.Qt__AlignRight) 163 lay.AddWidget3(d.preview, 1, 1, 1, 2, 0) 164 lay.AddWidget2(d.upload, 2, 1, core.Qt__AlignLeft) 165 lay.AddWidget2(d.download, 2, 2, core.Qt__AlignRight) 166 167 sBar := widgets.NewQStatusBar(nil) 168 MainWindow.StatusBar().ConnectMessageChanged(func(message string) { 169 sBar.ShowMessage(message, 0) 170 sBar.Repaint() 171 }) 172 lay.AddWidget3(sBar, 3, 0, 1, 3, 0) 173 d.SetModal(true) 174 return &d 175 } 176 177 func (d Documents) activateItem(id rd.IId) { 178 d.refresh.SetEnabled(true) 179 d.upload.SetEnabled(true) 180 d.download.SetEnabled(true) 181 d.showMiniature(id.Int64()) 182 } 183 184 // RenderDocuments calcule les documents à afficher, 185 // met à jour l'affichage, et lance le chargement des miniatures manquantes 186 func (d Documents) RenderDocuments() { 187 var liste rd.Table 188 // aides / personnes ; target courante 189 if d.forAide { 190 ct := d.main.Controllers.Aides 191 if idAide := ct.Etat.AideCurrent; idAide != nil { 192 liste = ct.Base.NewAide(idAide.Int64()).GetDocuments() 193 } else { 194 liste = ct.Base.GetAllDocuments(true) 195 } 196 } else { 197 ct := d.main.Controllers.Personnes 198 if idPersonne, ok := ct.Etat.ItemCurrent.(rd.IdPersonne); ok { 199 docs := ct.Base.NewPersonne(idPersonne.Int64()).GetDocuments(nil) 200 liste = dm.DocumentsPersonnesToAcces(docs) 201 } else { 202 liste = ct.Base.GetAllDocuments(false) 203 } 204 } 205 currentItem, has := d.documents.CurrentData() 206 d.documents.Model().SetData(liste) 207 if has { 208 d.documents.SelectRow(currentItem.Id) 209 d.activateItem(currentItem.Id) 210 } 211 d.completeMiniatures() 212 } 213 214 func (d Documents) showMiniature(id int64) { 215 im := d.miniatures.Read(id) 216 size := len(im) 217 if size == 0 { 218 d.preview.SetText("<i>Aucune miniature</i>") 219 return 220 } 221 image := gui.QImage_FromData(im, size, "") 222 d.preview.SetPixmap(gui.NewQPixmap().FromImage(image, 0)) 223 } 224 225 func (d Documents) completeMiniatures() { 226 var needed rd.Ids 227 already := d.miniatures.Keys() 228 for _, doc := range d.documents.Model().GetData() { 229 id := doc.Id.Int64() 230 if !already[id] { 231 needed = append(needed, id) 232 } 233 } 234 go d.loadMiniatures(needed) 235 } 236 237 func (d Documents) loadMiniatures(ids rd.Ids) { 238 if len(ids) == 0 { 239 return 240 } 241 d.main.ShowStandard("Chargement des miniatures...", true) 242 out, err := controllers.LoadMiniatures(ids) 243 if err != nil { 244 d.main.ShowStandard(fmt.Sprintf("Erreur pendant le chargement des miniatures : %s", err), false) 245 return 246 } 247 d.miniatures.WriteMany(out) 248 d.main.ShowStandard("Miniatures téléchargées.", false) 249 } 250 251 func (d Documents) supprimeDocument(idDoc int64) { 252 doc := d.main.Base.Documents[idDoc] 253 dial := basic.Dialog("Confirmation") 254 dial.Layout().AddWidget(basic.Label(fmt.Sprintf("Confirmez-vous la <b>suppression</b> du document <i>%s</i> (%d KB) ? <br/> Attention, cette opération est irréversible.", doc.NomClient, doc.Taille/1000))) 255 valid := basic.Button("Supprimer") 256 valid.SetObjectName(basic.ONDelete) 257 valid.ConnectClicked(func(_ bool) { 258 dial.Accept() 259 }) 260 dial.Layout().AddWidget(valid) 261 if dial.Exec() == 0 { 262 d.main.ShowStandard("Suppression annulée", false) 263 return 264 } 265 d.SetEnabled(false) 266 d.main.SupprimeDocument(idDoc) 267 d.RenderDocuments() 268 d.SetEnabled(true) 269 } 270 271 func (d *Documents) ajouteDocument(_ bool) { 272 docPath := SelectFile() 273 if docPath == "" { 274 d.main.ShowStandard("Aucun fichier choisir, ajout annulé.", false) 275 return 276 } 277 278 type ficheDoc interface { 279 Fiche 280 SetData(document rd.Document, targetDocument rd.TargetDocument) 281 GetData() (rd.Document, rd.TargetDocument) 282 } 283 284 var ( 285 fi ficheDoc 286 target rd.TargetDocument 287 ) 288 289 if d.forAide { 290 fi = NewFicheDocumentAide(d.main.Base, d.editable) 291 if pc := d.main.Controllers.Aides.Etat.AideCurrent; pc != nil { 292 target = rd.DocumentAide{IdAide: pc.Int64()} 293 } 294 } else { 295 fi = NewFicheDocumentPersonne(d.main.Base, d.editable) 296 if pc, ok := d.main.Controllers.Personnes.Etat.ItemCurrent.(rd.IdPersonne); ok { 297 target = rd.DocumentPersonne{IdPersonne: pc.Int64()} 298 } 299 } 300 301 doc := rd.Document{NomClient: rd.String(filepath.Base(docPath))} 302 fi.SetData(doc, target) 303 det := NewDetails(fi, d.editable) 304 if det.Exec() == 0 { 305 d.main.ShowStandard("Ajout annulé.", false) 306 return 307 } 308 doc, target = fi.GetData() 309 310 d.SetEnabled(false) 311 d.Repaint() 312 mon := NewMonitorDownload("Téléversement vers le serveur...") 313 basic.Delay(func() { 314 d.main.CreeUploadDocument(docPath, doc, target, mon.Monitor) 315 mon.Close() 316 d.SetEnabled(true) 317 d.RenderDocuments() 318 }) 319 } 320 321 func (d *Documents) remplaceDocument(_ bool) { 322 item, ok := d.documents.CurrentData() 323 if !ok { 324 d.main.ShowError(errors.New("Veuillez choisir un document.")) 325 return 326 } 327 doc := d.main.Base.Documents[item.Id.Int64()] 328 docPath := SelectFile() 329 if docPath == "" { 330 d.main.ShowStandard("Aucun fichier choisit, remplacement annulé.", false) 331 return 332 } 333 d.SetEnabled(false) 334 d.Repaint() 335 mon := NewMonitorDownload("Téléversement vers le serveur...") 336 basic.Delay(func() { 337 out := d.main.UploadDocument(docPath, doc.Id, mon.Monitor) 338 mon.Close() 339 d.SetEnabled(true) 340 if out != nil { 341 d.refreshMiniature(out.Id) 342 } 343 d.RenderDocuments() 344 }) 345 } 346 347 func (d *Documents) downloadDocument(_ bool) { 348 item, ok := d.documents.CurrentData() 349 if !ok { 350 d.main.ShowError(errors.New("Veuillez choisir un document.")) 351 return 352 } 353 mon := NewMonitorDownload("Téléchargement du document...") 354 basic.Delay(func() { 355 path := d.main.DownloadDocument(item.Id.Int64(), mon.Monitor) 356 mon.Close() 357 basic.ShowFile(path) 358 }) 359 } 360 361 func (d *Documents) refreshMiniature(idDoc int64) { 362 d.loadMiniatures([]int64{idDoc}) 363 sizeMin := len(d.miniatures.Read(idDoc)) 364 msg := fmt.Sprintf("Miniature téléchargée (%d KB)", sizeMin/1000) 365 if sizeMin == 0 { 366 msg = "Miniature indisponible" 367 } 368 d.main.ShowStandard(msg, false) 369 d.showMiniature(idDoc) 370 } 371 372 var acceptedFiles = []string{"application/pdf", "image/jpeg", "image/png"} 373 374 func SelectFile() string { 375 var patterns []string 376 db := core.NewQMimeDatabase2() 377 for _, ty := range acceptedFiles { 378 mime := db.MimeTypeForName(ty) 379 if mime.IsValid() { 380 patterns = append(patterns, strings.Join(mime.GlobPatterns(), " ")) 381 } 382 } 383 nameFiler := "Acceptés (" + strings.Join(patterns, " ") + ")" 384 return widgets.QFileDialog_GetOpenFileName(nil, "Sélectionner un fichier", "", nameFiler, "", 0) 385 }