O método de visualização(CqRecord) agora lê a propriedade LEGAL_ACTIONS do registro, além da propriedade ALL_FIELD_VALUES. O valor da propriedade LEGAL_ACTIONS é uma lista de proxies de Ação para as ações que podem ser legalmente aplicadas ao registro em seu estado atual. Esta lista é usada para preencher um controle de caixa de combinação, a partir da qual o usuário pode selecionar uma ação antes de clicar no botão editar. Alguns tipos de ações, como SUBMIT e RECORD_SCRIPT_ALIAS não são suportadas por este exemplo e não são, portanto, incluídas no controle da caixa de combinação mesmo se presentes no conjunto de ações legais.
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});
O EditView.Viewer também suporta um botão Anexar e Separar para incluir e remover os arquivos anexados de um campo de anexo.
O botão Anexar apresenta um diálogo de seleção de arquivo Swing padrão para o usuário e permite que ele selecione o arquivo a ser anexado. Para anexar o arquivo ao registro, CqAttachment.doCreateAttachment() é usado, passando a ele o nome do arquivo selecionado pelo usuário. Antes que este método possa ser invocado, um proxy CqAttachment que remeta ao local adequado deve ser construído. A pasta na qual o recurso de anexo irá residir é o valor do campo ao qual ele está anexado - este é o campo atualmente selecionado na visualização do registro e, então, é obtido a partir do objeto do quadro retornado pelo ViewRecord.showRecord. Um nome exclusivo para o recurso do anexo é gerado a partir da hora atual do dia. Este nome é meramente um marcador já que o doCreateAttachment() é livre para alterar o nome do recurso para adequar-se a suas necessidades.
add.addActionListener(new ActionListener(){ /** * Solicita ao usuário o nome de um arquivo a ser anexado ao * campo selecionado. Se fornecido, o conteúdo do * arquivo é lido no banco de dados do ClearQuest como um anexo. */ 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);} }}});
O botão Separar usa o ViewRecord.selectAttachment para obter do usuário a identidade do anexo a ser removido em forma de um proxy CqAttachment. O método doUnbindAll deste proxy é, então, invocado para remover o anexo do banco de dados.
remove.addActionListener(new ActionListener(){ /** * Permite que o usuário selecione um anexo associado ao * campo selecionado. Se o usuário selecionar tal anexo * ele é excluído. */ 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);} } } );
O botão Editar busca o proxy CqAction selecionado a partir da caixa de combinação e passa esse proxy e o proxy de registro para o visualizador atual para o método de edição, o qual irá realmente iniciar a edição do registro. Se o CqAction.Type da ação selecionada for DUPLICADO, então o id do registro duplicado deve ser fornecido com a ação. Este valor é solicitado pelo usuário e é colocado no mapa de argumentos da Ação como o valor do argumento denominado original.
start.addActionListener(new ActionListener(){ /** * Começa a edição do registro usando a ação atualmente * selecionada pela caixa de combinação no diálogo de visualização. */ 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; }
O método de edição é invocado quando o usuário clica no botão Editar no visualizador de registros. É passado um proxy para o registro a ser editado e um proxy para a ação sob a qual a edição deve ser executada.
Observe que nenhuma propriedade precisa ser definida pelo proxy Ação usado para iniciar uma açãoClearQuest - apenas seu local e seu mapa de argumento (se necessário) deve ser definido. Para formar o local para uma ação, é necessário conhecer seu <nome>, o <record-type> do registro com que vai ser usado e o <banco de dados> e <db-set> onde o registro está localizado.
O local é, então, cq.action:<record-type>/<name>@<db-set>/<database>; um exemplo é, cq.action:Defect/Assign@7.0.0/SAMPL
Usando um novo proxy, CqRecord.doWriteProperties é invocado a iniciar a operação de edição. A única propriedade que deve ser gravada é a ação sob a qual o registro deve ser editado. O método doWriteProperties retorna um proxy para o registro que foi aberto para editar e esse proxy irá conter as propriedades solicitadas no RECORD_PROPERTIES PropertyRequest. As propriedades também poderiam ser obtidas usando record.doReadProperties (com o mesmo PropertyRequest) depois que o doWriteProperties retorna, mas isso seria menos eficiente como iria requerer outra roundtrip para o repositório para os mesmos dados.
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 é semelhante ao showRecord. A diferença primária é que ele define TableModel.isCellEditable and 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 retorna como true somente para a coluna VALUE e somente se as linhas pertencem a um campo do qual a REQUIREDNESS não é 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 define o novo valor do campo no proxy CqRecord associado com a exibição. Primeiro, a estrutura CqFieldValue é atualizada usando seu método initialize(). Este método aceita uma representação em cadeia para a maioria dos tipos de campos e irá converter a cadeia no tipo de dados apropriado para o campo.
Quando o CqFieldValue tiver sido atualizado, ele será definido no proxy CqRecord associado com o PropertyName para o campo. Esta etapa é necessária para que o valor do novo campo seja gravado no banco de dados quando o registro for consolidado. Sem esta etapa, o novo valor do campo permaneceria somente no objeto CqFieldValue que é parte da propriedade ALL_FIELD_VALUES. A propriedade ALL_FIELD_VALUES não é gravável, assim, a mudança nunca seria gravada no banco de dados. Copiando o CqFieldValue modificado diretamente para a entrada do proxy para o campo, esse campo se torna uma propriedade atualizada e o valor atualizado será gravado no banco de dados pelo próximo métododo, que é executado no 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); // Pedir para ser notificado das mudanças de seleção. ListSelectionModel rowSM = table.getSelectionModel(); rowSM.addListSelectionListener(new ListSelectionListener() { /** * Ativa os botões no diálogo com base no tipo de * campo seleciondo na tabela. */ 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); } } } });
O botão Visualizar é igual ao botão Visualizar em um ViewRecord.Viewer.
buttons.add(show); show.addActionListener(new ActionListener(){ /** * Invoca um método de visualização no valor do campo selecionado * na tabela. */ 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(campos, linha); if (record != null) { view(record); } else if (ViewRecord.isAttachmentList(fields, row)) { view(ViewRecord.selectAttachment(quadro, campos, linha, "Visualizar")); } } } });
O botão Entregar consolida o registro modificado no banco de dados chamando o doWriteProperties() com a opção CqProvider.DELIVER. Depois que a entrega ocorre, o Visualizador é minimizado e, em seguida, um novo Visualizador aparece no proxy original. Como o método view() relê as propriedades a partir do banco de dados, esta nova visualização irá exibir os valores atualizados.
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); } } });
O botão Cancelar abandona a operação de edição usando o método doRevert do proxy do registro. Como com o botão Entregar, o Visualizador é fechado e um novo Visualizador é instanciado para o proxy original.
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); } } });
Os botões Anexar e Separar são iguais àqueles para o ViewRecord.Viewer, exceto que o argumento do pedido de entrega é SUSPENDER ao invés de ENTREGAR. Isso irá consolidar a inclusão ou exclusão do anexo até que o registro inteiro seja consolidado pelo botão Entregar.
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; }
A GUI poderia executar uma chamada para o record.doWriteProperties cada vez que um campo é modificado, o que poderia dar o feedback imediato ao usuário, mas também poderia ser terrivelmente ineficiente dependendo do protocolo de comunicação entre o cliente e o servidor. A GUI também poderia fornecer um botão Atualizar, que faria com que todas as atualizações acumuladas fossem gravadas no servidor, sem realmente entregá-las. Isso daria ao esquema uma oportunidade de examinar os valores e relatar erros de retorno. Estas modificações são deixadas para o leitor e a abordagem tomada dependeria do uso pretendido deste aplicativo.