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  }