001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2011 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.gwt.ui;
021
022 import com.google.gwt.event.dom.client.ClickEvent;
023 import com.google.gwt.event.dom.client.ClickHandler;
024 import com.google.gwt.user.client.ui.*;
025 import com.google.gwt.widgetideas.table.client.PreloadedTable;
026 import org.sonar.wsclient.gwt.AbstractCallback;
027 import org.sonar.wsclient.gwt.Sonar;
028 import org.sonar.wsclient.services.Resource;
029 import org.sonar.wsclient.services.Source;
030 import org.sonar.wsclient.services.SourceQuery;
031
032 import java.util.*;
033
034 public abstract class SourcePanel extends Composite implements ClickHandler {
035
036 private static final int MAX_LINES_BY_BLOCK = 3000;
037
038 private final Panel panel = new VerticalPanel();
039 private PreloadedTable rowsTable;
040 private Source source;
041 private final Loading loading = new Loading();
042 private int from = 0;
043 private int length = 0;
044
045 private boolean started = false;
046 private Resource resource;
047 private boolean hasNoSources = false;
048
049 // pagination
050 private Button moreButton = null;
051 private int currentRow = 0;
052 private int offset = 0;
053 private boolean firstPage = true;
054 private boolean previousLineIsDecorated = true;
055 private boolean firstDecoratedLine = true;
056
057 public SourcePanel(Resource resource) {
058 this(resource, 0, 0);
059 }
060
061 public Resource getResource() {
062 return resource;
063 }
064
065 public SourcePanel(Resource resource, int from, int length) {
066 this.from = from;
067 this.length = length;
068 this.resource = resource;
069
070 panel.add(loading);
071 panel.getElement().setId("sourcePanel");
072 initWidget(panel);
073 setStyleName("gwt-SourcePanel");
074
075 loadSources();
076 }
077
078 public void onClick(ClickEvent clickEvent) {
079 if (clickEvent.getSource() == moreButton) {
080 hideMoreButton();
081 displaySource();
082 }
083 }
084
085 public Button getMoreButton() {
086 if (moreButton == null) {
087 moreButton = new Button("More");
088 moreButton.getElement().setId("more_source");
089 moreButton.addClickHandler(this);
090 }
091 return moreButton;
092 }
093
094 public void showMoreButton() {
095 hideMoreButton();
096 panel.add(getMoreButton());
097 }
098
099 public void hideMoreButton() {
100 getMoreButton().removeFromParent();
101 }
102
103
104 public void refresh() {
105 if (!hasNoSources) {
106 panel.clear();
107 panel.add(loading);
108
109 rowsTable = null;
110 currentRow = 0;
111 offset = 0;
112 firstPage = true;
113 previousLineIsDecorated = true;
114 firstDecoratedLine = true;
115 displaySource();
116 }
117 }
118
119 private void loadSources() {
120 Sonar.getInstance().find(SourceQuery.create(resource.getId().toString())
121 .setLinesFromLine(from, length)
122 .setHighlightedSyntax(true), new AbstractCallback<Source>(loading) {
123
124 @Override
125 protected void doOnResponse(Source result) {
126 source = result;
127 displaySource();
128 }
129
130 @Override
131 protected void doOnError(int errorCode, String errorMessage) {
132 if (errorCode == 404) {
133 panel.add(new HTML("<p style=\"padding: 5px\">No sources</p>"));
134 hasNoSources = true;
135 loading.removeFromParent();
136
137 } else if (errorCode == 401) {
138 panel.add(new HTML("<p style=\"padding: 5px\">You're not authorized to view source code</p>"));
139 hasNoSources = true;
140 loading.removeFromParent();
141
142 } else {
143 super.onError(errorCode, errorMessage);
144 }
145 }
146
147 });
148 }
149
150 protected void setStarted() {
151 started = true;
152 displaySource();
153 }
154
155 private void displaySource() {
156 if (started && source != null) {
157 createRowsTable();
158
159 int displayedLines = 0;
160 SortedMap<Integer, String> lines = source.getLinesById().subMap(offset, offset + source.getLinesById().lastKey() + 1);
161 Iterator<Map.Entry<Integer, String>> linesById = lines.entrySet().iterator();
162
163 while (displayedLines < MAX_LINES_BY_BLOCK && linesById.hasNext()) {
164 Map.Entry<Integer, String> entry = linesById.next();
165 Integer lineIndex = entry.getKey();
166 if (shouldDecorateLine(lineIndex)) {
167 if (!previousLineIsDecorated && !firstDecoratedLine) {
168 setRowHtml(0, "<div class='src' style='background-color: #fff;height: 3em; border-top: 1px dashed silver;border-bottom: 1px dashed silver;'> </div>");
169 setRowHtml(1, " ");
170 setRowHtml(2, " ");
171 setRowHtml(3, "<div class='src' style='background-color: #fff;height: 3em; border-top: 1px dashed silver;border-bottom: 1px dashed silver;'> </div>");
172 currentRow++;
173 }
174
175 List<Row> rows = decorateLine(lineIndex, entry.getValue());
176 if (rows != null) {
177 for (Row row : rows) {
178 setRowHtml(0, row.getColumn1());
179 setRowHtml(1, row.getColumn2());
180 setRowHtml(2, row.getColumn3());
181 setRowHtml(3, row.getColumn4());
182 currentRow++;
183 }
184 previousLineIsDecorated = true;
185 firstDecoratedLine = false;
186 }
187 displayedLines++;
188
189 } else {
190 previousLineIsDecorated = false;
191 }
192 offset++;
193 }
194
195 if (firstPage) {
196 panel.clear();
197 panel.add(rowsTable);
198 firstPage = false;
199 }
200
201 if (offset <= source.getLinesById().lastKey()) {
202 showMoreButton();
203 } else {
204 hideMoreButton();
205 }
206 }
207 }
208
209 private void setRowHtml(int colNum, String html) {
210 if (firstPage) {
211 rowsTable.setPendingHTML(currentRow, colNum, html);
212 } else {
213 rowsTable.setHTML(currentRow, colNum, html);
214 }
215 }
216
217 private void createRowsTable() {
218 if (rowsTable == null) {
219 rowsTable = new PreloadedTable();
220 rowsTable.setStyleName("sources code");
221
222 offset = source.getLinesById().firstKey();
223 if (shouldDecorateLine(0)) {
224 List<Row> rows = decorateLine(0, null);
225 if (rows != null) {
226 for (Row row : rows) {
227 rowsTable.setPendingHTML(currentRow, 0, row.getColumn1());
228 rowsTable.setPendingHTML(currentRow, 1, row.getColumn2());
229 rowsTable.setPendingHTML(currentRow, 2, row.getColumn3());
230 rowsTable.setPendingHTML(currentRow, 3, row.getColumn4());
231 currentRow++;
232 }
233 }
234 }
235 }
236 }
237
238 protected boolean shouldDecorateLine(int index) {
239 return true;
240 }
241
242 protected List<Row> decorateLine(int index, String source) {
243 if (index > 0) {
244 return Arrays.asList(new Row(index, source));
245 }
246 return null;
247 }
248
249 public static class Row {
250 protected String column1;
251 protected String column2;
252 protected String column3;
253 protected String column4;
254
255 public Row(String column1, String column2, String column3) {
256 this.column1 = column1;
257 this.column2 = column2;
258 this.column3 = column3;
259 this.column4 = "";
260 }
261
262 public Row(String column1, String column2, String column3, String column4) {
263 this.column1 = column1;
264 this.column2 = column2;
265 this.column3 = column3;
266 this.column4 = column4;
267 }
268
269 public Row(int lineIndex, String source) {
270 setLineIndex(lineIndex, "");
271 unsetValue();
272 setSource(source, "");
273 }
274
275 public Row() {
276 }
277
278 public Row setLineIndex(int index, String style) {
279 column1 = "<div class='ln " + style + "'>" + index + "</div>";
280 return this;
281 }
282
283 public Row setValue(String value, String style) {
284 column2 = "<div class='val " + style + "'>" + value + "</div>";
285 return this;
286 }
287
288 public Row setValue2(String value, String style) {
289 column3 = "<div class='val " + style + "'>" + value + "</div>";
290 return this;
291 }
292
293 public Row unsetValue() {
294 column2 = "";
295 column3 = "";
296 return this;
297 }
298
299 public Row setSource(String source, String style) {
300 column4 = "<div class='src " + style + "'><pre>" + source + "</pre></div>";
301 return this;
302 }
303
304 public String getColumn1() {
305 return column1;
306 }
307
308 public void setColumn1(String column1) {
309 this.column1 = column1;
310 }
311
312 public String getColumn2() {
313 return column2;
314 }
315
316 public void setColumn2(String column2) {
317 this.column2 = column2;
318 }
319
320 public String getColumn3() {
321 return column3;
322 }
323
324 public void setColumn3(String column3) {
325 this.column3 = column3;
326 }
327
328 public String getColumn4() {
329 return column4;
330 }
331
332 public void setColumn4(String column4) {
333 this.column4 = column4;
334 }
335 }
336 }