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 */
020package org.sonar.plugins.design.ui.page.client;
021
022import com.google.gwt.event.dom.client.ClickEvent;
023import com.google.gwt.event.dom.client.ClickHandler;
024import com.google.gwt.event.dom.client.DoubleClickEvent;
025import com.google.gwt.event.dom.client.DoubleClickHandler;
026import com.google.gwt.i18n.client.Dictionary;
027import com.google.gwt.user.client.Window;
028import com.google.gwt.user.client.ui.*;
029import org.sonar.gwt.Links;
030import org.sonar.gwt.ui.Icons;
031
032import java.util.LinkedList;
033import java.util.List;
034
035public 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}