001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2012 SonarSource
004     * mailto:contact AT sonarsource DOT com
005     *
006     * Sonar is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU Lesser General Public
008     * License as published by the Free Software Foundation; either
009     * version 3 of the License, or (at your option) any later version.
010     *
011     * Sonar is distributed in the hope that it will be useful,
012     * but WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * Lesser General Public License for more details.
015     *
016     * You should have received a copy of the GNU Lesser General Public
017     * License along with Sonar; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019     */
020    package org.sonar.plugins.design.ui.page.client;
021    
022    import com.google.gwt.event.dom.client.ClickEvent;
023    import com.google.gwt.event.dom.client.ClickHandler;
024    import com.google.gwt.event.dom.client.DoubleClickEvent;
025    import com.google.gwt.event.dom.client.DoubleClickHandler;
026    import com.google.gwt.i18n.client.Dictionary;
027    import com.google.gwt.user.client.Window;
028    import com.google.gwt.user.client.ui.*;
029    import org.sonar.gwt.Links;
030    import org.sonar.gwt.ui.Icons;
031    
032    import java.util.LinkedList;
033    import java.util.List;
034    
035    public class Dsm extends Composite {
036    
037      /* STYLES */
038      public static final String DSM = "dsm";
039    
040      public static final String HEADER = "htable";
041      public static final String HEADER_TITLE = "ht";
042      public static final String HEADER_SELECTED_SUFFIX = "s";
043      public static final String HEADER_INDICATOR = "hi";
044      public static final String HEADER_HIGHER_INDICATOR_SUFFIX = "h";
045      public static final String HEADER_LOWER_INDICATOR_SUFFIX = "l";
046    
047      public static final String GRID = "gtable";
048      public static final String GRID_CELL_BOTTOM_LEFT = "cbl";
049      public static final String GRID_CELL_TOP_RIGHT = "ctr";
050      public static final String GRID_CELL_DIAGONAL = "cd";
051      public static final String GRID_CELL_SELECTION1_SUFFIX = "s1";
052      public static final String GRID_CELL_SELECTION2_SUFFIX = "s2";
053      public static final String GRID_CELL_COMB1_SUFFIX = "c1";
054      public static final String GRID_CELL_COMB2_SUFFIX = "c2";
055      public static final String[] GRID_SUFFIXES = {GRID_CELL_SELECTION1_SUFFIX, GRID_CELL_SELECTION2_SUFFIX, GRID_CELL_COMB1_SUFFIX, GRID_CELL_COMB2_SUFFIX};
056    
057    
058      private VerticalPanel dsm = new VerticalPanel();
059      private DsmData.Rows data;
060      private Label[][] cells;
061      private Label[] titles;
062      private Label[] indicators;
063      private List<Label> highlightedCells = new LinkedList<Label>();
064    
065      public Dsm() {
066        dsm.setStylePrimaryName(DSM);
067        initWidget(dsm);
068      }
069    
070      private Widget createLegend() {
071        Dictionary l10n = Dictionary.getDictionary("l10n");
072        HorizontalPanel legend = new HorizontalPanel();
073        legend.getElement().setId("dsmlegend");
074        legend.add(new HTML("<div class='square gray'> </div>"));
075        legend.add(new Label(l10n.get("design.legend.dependencies")));
076        legend.add(new HTML("<div class='space'></div>"));
077        legend.add(new HTML("<div class='square red'> </div> "));
078        legend.add(new Label(l10n.get("design.legend.cycles")));
079        legend.add(new HTML(" <div class='space'></div> "));
080        legend.add(new HTML("<div class='square green'></div> "));
081        legend.add(new Label(l10n.get("design.legend.uses")));
082        legend.add(new HTML("<div class='square blue'></div> "));
083        legend.add(new Label(l10n.get("design.legend.uses")));
084        legend.add(new HTML(" <div class='square yellow'></div>"));
085        return legend;
086      }
087    
088      public void displayNoData() {
089        dsm.clear();
090        dsm.add(new Label(Dictionary.getDictionary("l10n").get("noData")));
091      }
092    
093      public void display(DsmData.Rows data) {
094        if (data == null) {
095          displayNoData();
096          
097        } else {
098          this.data = data;
099          dsm.clear();
100          dsm.add(createHelp());
101          dsm.add(createLegend());
102          HorizontalPanel matrix = new HorizontalPanel();
103          matrix.add(createRowHeader());
104          matrix.add(createGrid());
105          dsm.add(matrix);
106        }
107      }
108    
109      private Widget createHelp() {
110        HorizontalPanel help = new HorizontalPanel();
111        help.getElement().setId("dsmhelp");
112        Dictionary l10n = Dictionary.getDictionary("l10n");
113        Anchor link = new Anchor(l10n.get("design.help"), "http://docs.codehaus.org/x/QQFhC", "docsonar");
114        help.add(Icons.get().help().createImage());
115        help.add(link);
116        return help;
117      }
118    
119      private Grid createRowHeader() {
120        Grid header = new Grid(data.size(), 2);
121        header.setCellPadding(0);
122        header.setCellSpacing(0);
123        header.setStylePrimaryName(HEADER);
124    
125        titles = new Label[data.size()];
126        indicators = new Label[data.size()];
127        for (int indexRow = 0; indexRow < data.size(); indexRow++) {
128          DsmData.Row row = data.get(indexRow);
129    
130          HTML title = buildRowTitle(indexRow, row);
131          titles[indexRow] = title;
132          header.setWidget(indexRow, 0, title);
133    
134          Label indicator = buildLabel("", HEADER_INDICATOR);
135          header.setWidget(indexRow, 1, indicator);
136          indicators[indexRow] = indicator;
137        }
138        return header;
139      }
140    
141      private Grid createGrid() {
142        int rowsCount = data.size();
143        Grid grid = new Grid(rowsCount, rowsCount);
144        grid.setCellPadding(0);
145        grid.setCellSpacing(0);
146        grid.setStylePrimaryName(GRID);
147    
148        return loadGridCells(grid, data);
149      }
150    
151      private Grid loadGridCells(Grid grid, DsmData.Rows data) {
152        int size = data.size();
153        cells = new Label[size][size];
154        for (int row = 0; row < size; row++) {
155          DsmData.Row resource = data.get(row);
156          for (int col = 0; col < resource.size(); col++) {
157            Label cell = createGridCell(row, col, resource.getWeight(col));
158            grid.setWidget(row, col, cell);
159            cells[row][col] = cell;
160          }
161        }
162        return grid;
163      }
164    
165    
166      /* ---------------- ACTIONS -------------------- */
167    
168      public void onCellClicked(int row, int col) {
169        cancelHighlighting();
170    
171        highlightTitle(row);
172        highlightTitle(col);
173        highlightIndicator(row);
174        highlightIndicator(col);
175    
176        for (int i = 0; i < cells.length; i++) {
177          for (int j = 0; j < cells.length; j++) {
178            Label cell = cells[i][j];
179            if (i == row && j == col) {
180              highlightCell(cell, GRID_CELL_SELECTION1_SUFFIX);
181    
182            } else if (j == row && i == col) {
183              // opposite
184              highlightCell(cell, GRID_CELL_SELECTION1_SUFFIX);
185    
186            } else if (j == col || i == col) {
187              highlightCell(cell, GRID_CELL_COMB1_SUFFIX);
188    
189            } else if (i == row || j == row) {
190              highlightCell(cell, GRID_CELL_COMB2_SUFFIX);
191            }
192          }
193        }
194      }
195    
196      private void displayDependencyInfo(int row, int col) {
197        DsmData.Cell cell = data.get(row).getCell(col);
198        DependencyInfo.getInstance().showOrPopup(cell.getDependencyId());
199      }
200    
201      public void onTitleClicked(int row) {
202        cancelHighlighting();
203        highlightTitle(row);
204        highlightIndicator(row);
205    
206        // highlight row
207        for (int col = 0; col < cells[row].length; col++) {
208          highlightCell(cells[row][col], GRID_CELL_SELECTION2_SUFFIX);
209          if (col < row && hasWeight(cells[row][col])) {
210            highlightIndicator(col, true);
211          }
212        }
213    
214        // highlight column
215        for (int i = 0; i < cells.length; i++) {
216          if (i != row) {
217            highlightCell(cells[i][row], GRID_CELL_SELECTION2_SUFFIX);
218            if (i > row && hasWeight(cells[i][row])) {
219              highlightIndicator(i, false);
220            }
221          }
222        }
223      }
224    
225      private boolean hasWeight(Label label) {
226        return label.getText().length() > 0;
227      }
228    
229    
230      /*--------- EFFECTS ----------*/
231      private void cancelHighlighting() {
232        cancelGridHighlighting();
233        cancelIndicatorsHighlighting();
234        cancelTitlesHighlighting();
235      }
236    
237      private void cancelGridHighlighting() {
238        for (Label cell : highlightedCells) {
239          for (String suffix : GRID_SUFFIXES) {
240            cell.removeStyleDependentName(suffix);
241          }
242        }
243        highlightedCells.clear();
244      }
245    
246      private void highlightCell(Label cell, String style) {
247        cell.addStyleDependentName(style);
248        highlightedCells.add(cell);
249      }
250    
251      private void highlightTitle(int row) {
252        titles[row].addStyleDependentName(HEADER_SELECTED_SUFFIX);
253      }
254    
255      private void cancelTitlesHighlighting() {
256        for (Label title : titles) {
257          title.removeStyleDependentName(HEADER_SELECTED_SUFFIX);
258        }
259      }
260    
261      private void cancelIndicatorsHighlighting() {
262        for (Label indicator : indicators) {
263          indicator.removeStyleDependentName(HEADER_HIGHER_INDICATOR_SUFFIX);
264          indicator.removeStyleDependentName(HEADER_LOWER_INDICATOR_SUFFIX);
265          indicator.removeStyleDependentName(HEADER_SELECTED_SUFFIX);
266        }
267      }
268    
269      private void highlightIndicator(int row) {
270        indicators[row].addStyleDependentName(HEADER_SELECTED_SUFFIX);
271      }
272    
273      private void highlightIndicator(int row, boolean higher) {
274        indicators[row].addStyleDependentName(higher ? HEADER_HIGHER_INDICATOR_SUFFIX : HEADER_LOWER_INDICATOR_SUFFIX);
275      }
276    
277    
278      /* ---------- COMPONENTS ------------ */
279      private Label createGridCell(final int row, final int col, final int weight) {
280        Label cell;
281        if (row == col) {
282          cell = createDiagonalCell(row);
283    
284        } else {
285          cell = createNonDiagonalCell(row, col, weight);
286        }
287        return cell;
288      }
289    
290      private Label createNonDiagonalCell(final int row, final int col, int weight) {
291        Label cell;
292        cell = buildCell(row, col, weight, (col > row ? GRID_CELL_TOP_RIGHT : GRID_CELL_BOTTOM_LEFT));
293    
294        if (weight > 0) {
295          String tooltip = data.get(col).getName() + " -> " + data.get(row).getName() + " (" + weight + "). " + Dictionary.getDictionary("l10n").get("design.cellTooltip");
296          cell.setTitle(tooltip);
297        }
298        return cell;
299      }
300    
301      private Label createDiagonalCell(final int row) {
302        Label cell;
303        cell = buildLabel("-", GRID_CELL_DIAGONAL);
304        cell.addClickHandler(new ClickHandler() {
305          public void onClick(final ClickEvent event) {
306            onTitleClicked(row);
307          }
308        });
309        return cell;
310      }
311    
312      private HTML buildRowTitle(final int indexRow, final DsmData.Row row) {
313        HTML title = new HTML(Icons.forQualifier(row.getQualifier()).getHTML() + " " + row.getName()) {
314          {
315            addDomHandler(new DoubleClickHandler() {
316              public void onDoubleClick(DoubleClickEvent pEvent) {
317                if (row.getId() != null) {
318                  if (!"FIL".equals(row.getQualifier()) && !"CLA".equals(row.getQualifier())) {
319                    Window.Location.assign(Links.urlForResourcePage(row.getId(), DesignPage.GWT_ID, null));
320                  } else {
321                    Links.openMeasurePopup(row.getId(), null);
322                  }
323                }
324              }
325            }, DoubleClickEvent.getType());
326          }
327        };
328        title.setStylePrimaryName(HEADER_TITLE);
329        title.setTitle(Dictionary.getDictionary("l10n").get("design.rowTooltip"));
330        final int finalIndexRow = indexRow;
331        title.addClickHandler(new ClickHandler() {
332          public void onClick(ClickEvent event) {
333            onTitleClicked(finalIndexRow);
334          }
335        });
336        return title;
337      }
338    
339      private static Label buildLabel(String text, String primaryStyle) {
340        Label label = new Label(text);
341        label.setStylePrimaryName(primaryStyle);
342        return label;
343      }
344    
345      private Label buildCell(final int row, final int col, int weight, String primaryStyle) {
346        String text = "";
347        if (weight > 0) {
348          text = "<span>" + Integer.toString(weight) + "</span>";
349        }
350    
351        HTML cell = new HTML(text) {
352          {
353            addDomHandler(new DoubleClickHandler() {
354              public void onDoubleClick(DoubleClickEvent pEvent) {
355                displayDependencyInfo(row, col);
356              }
357            }, DoubleClickEvent.getType());
358          }
359        };
360        cell.addClickHandler(new ClickHandler() {
361          public void onClick(final ClickEvent event) {
362            onCellClicked(row, col);
363          }
364        });
365        cell.setStylePrimaryName(primaryStyle);
366        return cell;
367      }
368    
369    }