La méthode view(CqRecord) lit ensuite la propriété LEGAL_ACTIONS de l'enregistrement en plus de la propriété ALL_FIELD_VALUES. La valeur de la propriété LEGAL_ACTIONS est une liste de proxys d'action destinée aux actions qui peuvent être légalement appliquées à l'enregistrement dans son état actuel. Cette liste permet de remplir un contrôle de zone de liste déroulante, à partir de laquelle l'utilisateur peut sélectionner une action avant de cliquer sur le bouton d'édition. Certains types d'action, tels que SUBMIT et RECORD_SCRIPT_ALIAS ne sont pas pris en charge par cet exemple et, par conséquent, ne sont pas ajoutés au contrôle de zone de liste déroulante, même s'ils sont présents dans l'ensemble d'actions légales.
public JFrame view(CqRecord selected) { try { final CqRecord record = (CqRecord)selected.doReadProperties(RECORD_PROPERTIES); final JButton start = new JButton("Edit"); final JComboBox choices = new JComboBox(); for (CqAction a: record.getLegalActions()) { if (a.getType() != CqAction.Type.IMPORT && a.getType() != CqAction.Type.SUBMIT && a.getType() != CqAction.Type.BASE && a.getType() != CqAction.Type.RECORD_SCRIPT_ALIAS) choices.addItem(a); } final JButton add = new JButton("Attach"); final JButton remove = new JButton("Detach"); final ViewRecord.RecordFrame frame = showRecord("View: ", record, choices.getItemCount()==0? null: new JComponent[]{choices, start, add, remove});
EditView.Viewer prend également en charge des boutons Attach et Detach permettant d'ajouter et de supprimer les fichiers liés d'une zone de liaison.
Le bouton Attach présente une boîte de dialogue de sélection de fichier Swing standard à l'utilisateur et lui permet de sélectionner le fichier à lier. Pour lier un fichier à un enregistrement, CqAttachment.doCreateAttachment() est utilisé, qui transmet le nom du fichier sélectionné par l'utilisateur. Avant de pouvoir appeler cette méthode, un proxy CqAttachment adressant l'emplacement approprié doit être créé. Le dossier dans lequel la ressource de liaison résidera est la valeur de la zone à laquelle elle est liée - il s'agit de la zone actuellement sélectionnée dans la vue d'enregistrement et elle est donc obtenue à partir de l'objet de trame renvoyé par ViewRecord.showRecord. Un nom unique pour la ressource de lien est généré à partir de l'heure de la journée en cours. Ce nom est simplement un emplacement réservé car doCreateAttachment() est libre de modifier le nom de la ressource en fonction de ses besoins.
add.addActionListener(new ActionListener(){ /** * Demande à l'utilisateur d'entrer le nom d'un fichier lié à * la zone sélectionnée. S'il est ainsi fourni, le contenu du fichier * est lu dans la base de données ClearQuest en tant que fichier lié. */ public void actionPerformed(ActionEvent arg0) { if (!ViewRecord.isAttachmentList(frame.m_fields, frame.m_table.getSelectedRow())) return; JFileChooser chooser = new JFileChooser(); if (chooser.showOpenDialog(frame) == JFileChooser.APPROVE_OPTION) { try { String filename = chooser.getSelectedFile().getAbsolutePath(); CqFieldValue field = frame.m_fields.get(frame.m_table.getSelectedRow()); StpLocation aLoc = (StpLocation) ((StpFolder)field.getValue()).location() .child("new" + System.currentTimeMillis()); CqAttachment attachment = record.cqProvider().cqAttachment(aLoc); attachment = attachment.doCreateAttachment(filename, null, CqProvider.DELIVER); JOptionPane.showMessageDialog(frame, "Added '" + filename + "' as " + attachment); frame.dispose(); view(record); } catch(Throwable t) { Utilities.exception(frame, "Add Attachment", t);} }}});
Le bouton Detach utilise ViewRecord.selectAttachment pour obtenir de l'utilisateur d'identité du lien à supprimer sous la forme d'un proxy CqAttachment. La méthode doUnbindAll de ce proxy est alors appelée pour supprimer le lien de la base de données.
remove.addActionListener(new ActionListener(){ /** * Permet à l'utilisateur de sélectionner un lien associé * au fichier sélectionné. Si l'utilisateur sélectionne ce lien, * ce dernier est supprimé. */ public void actionPerformed(ActionEvent arg0) { if (!ViewRecord.isAttachmentList(frame.m_fields, frame.m_table.getSelectedRow())) return; try { CqAttachment attachment = ViewRecord.selectAttachment (frame, frame.m_fields, frame.m_table.getSelectedRow(), "Remove"); if (attachment != null) { attachment.doUnbindAll(null); frame.dispose(); view(record); } } catch(Throwable t) { Utilities.exception(frame, "Remove Attachment", t);} } } );
Le bouton Edit extrait le proxy CqAction sélectionné de la zone de liste déroulante et le transmet ainsi que le proxy d'enregistrement de l'afficheur en cours à la méthode d'édition, qui initiera l'édition de l'enregistrement. Si le CqAction.Type de l'action sélectionnée est DUPLICATE, l'ID de l'enregistrement en double doit être fourni avec l'action. Cette valeur est demandée à l'utilisateur et est placée dans la mappe d'arguments de l'action sous forme d'une valeur d'argument nommée original.
start.addActionListener(new ActionListener(){ /** * Commence l'édition de l'enregistrement à l'aide de l'action actuellement * sélectionnée par la zone de liste déroulante de la boîte de dialogue d'affichage. */ public void actionPerformed(ActionEvent arg0) { CqAction action = (CqAction)choices.getSelectedItem(); try { if (action.getType() == CqAction.Type.DUPLICATE) { String id = JOptionPane.showInputDialog (frame, "Enter ID of duplicated record"); if (id == null) return; action.argumentMap(new Hashtable<String>()); action.argumentMap().put("original", record.cqProvider().cqRecord((StpLocation)record .location().parent().child(id))); } edit(record, action); frame.dispose(); } catch (Exception ex) { Utilities.exception(frame, "Duplicate Action", ex); } } }); return frame; } catch (WvcmException ex){ ex.printStackTrace(); } return null; }
La méthode d'édition est appelée lorsque l'utilisateur clique sur le bouton Edit dans l'afficheur d'enregistrement. Sont transmis un proxy de l'enregistrement à éditer et un proxy de l'action sous laquelle l'édition doit être effectuée.
Notez qu'aucune propriété ne doit être définie par le proxy d'action utilisé pour démarrer une action ClearQuest - seuls son emplacement et sa mappe d'arguments (si nécessaire) doivent être définis. Pour former l'emplacement d'une action, vous devez connaître son <nom>, le <type-enregistrement> de l'enregistrement avec lequel elle va être utilisée et la <base-de-données> et le <jeu-bd> dans lesquels l'enregistrement est placé.
L'emplacement est alors cq.action:<type-enregistrement>/<nom>@<jeu-bd>/<base-de-données>; un exemple est cq.action:Defect/Assign@7.0.0/SAMPL
L'utilisation d'un nouveau proxy, CqRecord.doWriteProperties est appelée pour commencer l'opération d'édition. La seule propriété qui doit être écrite est l'action sous laquelle l'enregistrement doit être édité. La méthode doWriteProperties renvoie un proxy pour l'enregistrement qui a été ouvert pour l'édition et ce proxy contiendra les propriétés demandées dans la demande PropertyRequest RECORD_PROPERTIES. Les propriétés peuvent également être obtenues à l'aide de record.doReadProperties (avec la même PropertyRequest) après le renvoi de doWriteProperties, mais ce serait moins efficace car cela demanderait un nouvel aller-retour vers le référentiel pour les mêmes données.
public void edit(CqRecord selected, CqAction action) { try { CqRecord record = m_provider.cqRecord(selected.stpLocation()); record = (CqRecord) record.setAction(action) .doWriteProperties(RECORD_PROPERTIES, CqProvider.HOLD); editRecord("Edit: ", record, selected); } catch (WvcmException ex){ Utilities.exception(null, "Start Action", ex); } }
Viewer.editRecord est similaire à showRecord. La différence principale est qu'il définit TableModel.isCellEditable et TableModel.setValue.
JFrame editRecord(String title, final CqRecord record, final CqRecord selected) throws WvcmException { final JFrame frame = new JFrame(title + record.getUserFriendlyLocation()); JPanel panel = new JPanel(new BorderLayout()); JPanel buttons = new JPanel(new FlowLayout()); final JButton show = new JButton("View"); final JButton add = new JButton("Add"); final JButton remove = new JButton("Remove"); final JButton cancel = new JButton("Cancel"); final StpProperty.List<CqFieldValue>> fields = record.getAllFieldValues(); TableModel dataModel = new AbstractTableModel() { public int getColumnCount() { return fieldMetaProperties.length; } public int getRowCount() { return fields.size();} public String getColumnName(int col) { return fieldMetaProperties[col].getRoot().getName(); } public Object getValueAt(int row, int col) { try { return fields.get(row).getMetaProperty((MetaPropertyName<?>) fieldMetaProperties[col].getRoot()); } catch(Throwable ex) { if (ex instanceof StpException) { return ((StpException)ex).getStpReasonCode(); } else { String name = ex.getClass().getName(); return name.substring(name.lastIndexOf(".")+1); } } }
TableModel.isCellEditable renvoie true uniquement pour la colonne VALUE et seulement si la ligne appartient à une zone dont REQUIREDNESS n'est pas READ_ONLY.
public boolean isCellEditable(int row, int col) { if (fieldMetaProperties[col].getRoot().equals(StpProperty.VALUE)) { CqFieldValue field = fields.get(row); try { return field.getRequiredness() != Requiredness.READ_ONLY; } catch (WvcmException ex) { Utilities.exception(frame, "Field Requiredness", ex); } } return false; }
TableModel.setValueAt définit la nouvelle valeur de zone dans le proxy CqRecord associé à l'affichage. Tout d'abord, la structure de CqFieldValue est mise à jour à l'aide de sa méthode initialize(). Cette méthode accepte une représentation de chaîne pour la plupart des types de zones et convertit la chaîne en un type de données approprié pour la zone.
Une fois que CqFieldValue a été mis à jour, elle est définie dans le proxy CqRecord associé au PropertyName de la zone. Cette étape est nécessaire afin que la nouvelle valeur de zone soit écrite dans la base de données lorsque l'enregistrement est validé. Sans cette étape, la nouvelle valeur de zone resterait seulement dans l'objet CqFieldValue qui fait partie de la propriété ALL_FIELD_VALUES. La propriété ALL_FIELD_VALUES n'est pas accessible en écriture et le changement ne sera donc jamais écrit dans la base de données. En copiant la CqFieldValue modifiée directement dans l'entrée de proxy pour la zone, cette zone devient une propriété mise à jour et la valeur mise à jour sera écrite dans la base de données par la méthode do suivante qui est exécutée sur le proxy.
public void setValueAt(Object aValue, int row, int col) { if (fieldMetaProperties[col].getRoot().equals(StpProperty.VALUE)) { CqFieldValue<Object> field = fields.get(row); field.initialize(aValue); record.setFieldInfo(field.getFieldName(), field); } } private static final long serialVersionUID = 1L; }; final JTable table = new JTable(dataModel); table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); show.setEnabled(false); add.setEnabled(false); remove.setEnabled(false); // Demander à être notifié des changements de sélection. ListSelectionModel rowSM = table.getSelectionModel(); rowSM.addListSelectionListener(new ListSelectionListener() { /** * Active les boutons dans la boîte de dialogue en fonction du type de * zone sélectionnée dans la table. */ public void valueChanged(ListSelectionEvent e) { if (!e.getValueIsAdjusting()){ int[] selected = table.getSelectedRows(); show.setEnabled(false); add.setEnabled(false); remove.setEnabled(false); for (int i=0; i <selected.length; ++i) if (ViewRecord.getRecordReferencedAt(fields, selected[i]) != null) { show.setEnabled(true); } else if (ViewRecord.isAttachmentList(fields, selected[i])) { show.setEnabled(true); add.setEnabled(true); remove.setEnabled(true); } } } });
Le bouton View est le même que le bouton View dans ViewRecord.Viewer.
buttons.add(show); show.addActionListener(new ActionListener(){ /** * Appelle une méthode de visualisation sur la valeur de la zone sélectionnée * dans la table. */ public void actionPerformed(ActionEvent arg0) { int[] selected = table.getSelectedRows(); for (int i =0; i < selected.length; ++i) { int row = selected[i]; CqRecord record = ViewRecord.getRecordReferencedAt(fields, row); if (record != null) { view(record); } else if (ViewRecord.isAttachmentList(fields, row)) { view(ViewRecord.selectAttachment(frame, fields, row, "View")); } } } });
Le bouton Deliver valide l'enregistrement modifié sur la base de données en appelant doWriteProperties() avec l'option CqProvider.DELIVER. Une fois que la livraison a abouti, l'afficheur est arrêté et un nouvel afficheur est exécuté sur le proxy d'origine. Puisque la méthode view() lit à nouveau les propriétés à partir de la base de données, cette nouvelle vue affichera les valeurs mises à jour.
JButton deliver = new JButton("Deliver"); buttons.add(deliver); deliver.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { try { int mode = frame.getDefaultCloseOperation(); record.doWriteProperties(null, CqProvider.DELIVER); frame.dispose(); view(selected).setDefaultCloseOperation(mode); } catch (WvcmException ex) { Utilities.exception(frame, "Deliver failed", ex); } } });
Le bouton Cancel abandonne l'opération d'édition à l'aide de la méthode doRevert du proxy d'enregistrement. Comme pour le bouton Deliver, l'afficheur est fermé et un nouvel afficheur est instancié pour le proxy d'origine.
buttons.add(cancel); cancel.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { try { int mode = frame.getDefaultCloseOperation(); record.doRevert(null); frame.dispose(); if (mode == JFrame.EXIT_ON_CLOSE) System.exit(0); view(selected).setDefaultCloseOperation(mode); } catch (WvcmException ex) { Utilities.exception(frame, "Cancel failed", ex); } } });
Les boutons Attach et Detach sont les mêmes que ceux de ViewRecord.Viewer, excepté que l'argument d'ordre de livraison est HOLD à la place de DELIVER. Cela reportera la validation de l'ajout ou de la suppression du lien jusqu'à ce que l'ensemble de l'enregistrement soit validé par le bouton Deliver.
buttons.add(add); add.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showOpenDialog(frame); if (returnVal == JFileChooser.APPROVE_OPTION) { try { String filename = chooser.getSelectedFile().getAbsolutePath(); CqFieldValue field = fields.get(table.getSelectedRow()); StpLocation aLoc = (StpLocation) ((StpFolder)field.getValue()).location() .child("new" + System.currentTimeMillis()); CqAttachment attachment = record.cqProvider().cqAttachment(aLoc); attachment = attachment.doCreateAttachment(filename, null, CqProvider.HOLD); JOptionPane.showMessageDialog(frame, "Added '" + filename + "' as " + attachment + " (pending delivery)"); } catch(Throwable t) { Utilities.exception(frame, "Add Attachment", t);} }}}); buttons.add(remove); remove.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { try { CqAttachment attachment = ViewRecord.selectAttachment (frame, fields, table.getSelectedRow(), "Remove"); if (attachment != null) attachment.doUnbindAll(null); } catch(Throwable t) { Utilities.exception(frame, "Remove Attachment", t);} } } ); panel.add(new JScrollPane(table), BorderLayout.CENTER); panel.add(buttons, BorderLayout.SOUTH); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setContentPane(panel); frame.setBounds(300, 300, 600, 300); frame.setVisible(true); return frame; }
L'interface graphique pourrait effectuer un appel de record.doWriteProperties chaque fois qu'une zone est modifiée, ce qui donnerait à l'utilisateur un retour immédiat, mais peut être également très inefficace selon le protocole de communication utilisé entre le client et le serveur. L'interface graphique peut également fournir un bouton Update, qui permet d'écrire toutes les mises à jour accumulées sur le serveur, sans réellement les livrer. Cela donnerait au schéma l'opportunité d'examiner les valeurs et de signaler les erreurs. Ces modifications sont laissées au choix du lecteur et l'approche suivie dépend de l'utilisation prévue de l'application.