1 /* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 package ohos.devtools.views.trace; 17 18 import com.intellij.ui.JBColor; 19 import com.intellij.ui.components.JBPanel; 20 import com.intellij.util.Consumer; 21 import com.intellij.util.ui.JBUI; 22 import ohos.devtools.views.applicationtrace.util.TimeUtils; 23 import ohos.devtools.views.trace.util.Utils; 24 25 import java.awt.AlphaComposite; 26 import java.awt.Cursor; 27 import java.awt.Graphics; 28 import java.awt.Graphics2D; 29 import java.awt.Rectangle; 30 import java.awt.event.ComponentAdapter; 31 import java.awt.event.ComponentEvent; 32 import java.awt.event.KeyEvent; 33 import java.awt.event.KeyListener; 34 import java.awt.event.MouseEvent; 35 import java.awt.event.MouseListener; 36 import java.awt.event.MouseMotionListener; 37 import java.text.DecimalFormat; 38 import java.util.Arrays; 39 import java.util.HashMap; 40 import java.util.List; 41 import java.util.Map; 42 import java.util.Optional; 43 44 import static java.awt.event.KeyEvent.VK_A; 45 import static java.awt.event.KeyEvent.VK_D; 46 import static java.awt.event.KeyEvent.VK_S; 47 import static java.awt.event.KeyEvent.VK_W; 48 import static ohos.devtools.views.trace.TracePanel.DURATION; 49 import static ohos.devtools.views.trace.TracePanel.endNS; 50 import static ohos.devtools.views.trace.TracePanel.startNS; 51 52 /** 53 * The timescale 54 * 55 * @since 2021/5/12 16:39 56 */ 57 public class TimeShaft extends JBPanel implements KeyListener, MouseListener, MouseMotionListener { 58 private static final int SELECT_BORDER_WIDTH = 3; 59 60 private final ITimeRange rangeListener; 61 private final Consumer keyReleaseHandler; 62 private final Consumer mouseReleaseHandler; 63 private final AlphaComposite alpha20 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .2f); 64 private final AlphaComposite alpha40 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .4f); 65 private final AlphaComposite alpha100 = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1f); 66 private final long[] scales = 67 new long[] {50, 100, 200, 500, 1_000, 2_000, 5_000, 10_000, 20_000, 50_000, 100_000, 200_000, 500_000, 68 1_000_000, 2_000_000, 5_000_000, 10_000_000, 20_000_000, 50_000_000, 100_000_000, 200_000_000, 500_000_000, 69 1_000_000_000, 2_000_000_000, 5_000_000_000L, 10_000_000_000L, 20_000_000_000L, 50_000_000_000L, 70 100_000_000_000L, 200_000_000_000L, 500_000_000_000L}; 71 private int startX; 72 private int endX; 73 private Rectangle selectRect = new Rectangle(); 74 private Rectangle selectLeftRect = new Rectangle(); 75 private Rectangle selectRightRect = new Rectangle(); 76 private Rectangle selectTopRect = new Rectangle(); 77 private Cursor wCursor = new Cursor(Cursor.W_RESIZE_CURSOR); 78 private Cursor eCursor = new Cursor(Cursor.E_RESIZE_CURSOR); 79 private Cursor handCursor = new Cursor(Cursor.HAND_CURSOR); 80 private Cursor defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR); 81 private Status status = Status.DRAG; 82 private int offset; 83 private int length; 84 private double ratio1; 85 private double ratio2; 86 private double wheelNS; 87 private long scale; 88 private boolean isInit; 89 private Consumer<Graphics2D> timeShaftConsumer; 90 private DecimalFormat formatter = new DecimalFormat("#.##%"); 91 private Map<Integer, Double> rateMap = new HashMap<>(); 92 93 /** 94 * TimeShaft 95 * 96 * @param range range 97 * @param keyReleaseHandler keyReleaseHandler 98 * @param mouseReleaseHandler mouseReleaseHandler 99 */ TimeShaft(ITimeRange range, Consumer<KeyEvent> keyReleaseHandler, Consumer<MouseEvent> mouseReleaseHandler)100 public TimeShaft(ITimeRange range, Consumer<KeyEvent> keyReleaseHandler, Consumer<MouseEvent> mouseReleaseHandler) { 101 this.rangeListener = range; 102 this.keyReleaseHandler = keyReleaseHandler; 103 this.mouseReleaseHandler = mouseReleaseHandler; 104 this.setOpaque(true); 105 this.addComponentListener(new ComponentAdapter() { 106 @Override 107 public void componentResized(ComponentEvent event) { 108 super.componentResized(event); 109 startX = (int) (getWidth() * ratio1); 110 endX = (int) (getWidth() * ratio2); 111 Utils.setX(selectRect, Math.min(startX, endX)); 112 selectRect.width = Math.abs(endX - startX); 113 setAllRect(); 114 } 115 }); 116 addMouseListener(this); 117 addKeyListener(this); 118 addMouseMotionListener(this); 119 } 120 121 /** 122 * mouseClicked function 123 * 124 * @param event event 125 */ mouseClicked(MouseEvent event)126 public void mouseClicked(MouseEvent event) { 127 event.getX(); 128 } 129 130 /** 131 * set TimeShaft Consumer function 132 * 133 * @param consumer consumer 134 */ setTimeShaftConsumer(Consumer<Graphics2D> consumer)135 public void setTimeShaftConsumer(Consumer<Graphics2D> consumer) { 136 this.timeShaftConsumer = consumer; 137 } 138 139 /** 140 * set TimeShaft Consumer function 141 * 142 * @param event event 143 */ mousePressed(MouseEvent event)144 public void mousePressed(MouseEvent event) { 145 if (getVisibleRect().contains(event.getPoint())) { 146 if (status == Status.DRAG) { 147 startX = event.getX(); 148 endX = event.getX() + SELECT_BORDER_WIDTH * 3; 149 Utils.setX(selectRect, event.getX()); 150 Utils.setY(selectRect, 0); 151 selectRect.height = getHeight(); 152 selectRect.width = SELECT_BORDER_WIDTH * 3; 153 setAllRect(); 154 notifyRangeChange(startX, endX); 155 } else if (status == Status.MOVE) { 156 offset = Math.abs(event.getX() - Math.min(startX, endX)); 157 length = Math.abs(endX - startX); 158 notifyRangeChange(startX, endX); 159 } else { 160 notifyRangeChange(startX, endX); 161 } 162 } 163 } 164 165 /** 166 * when the mouse released event 167 * 168 * @param event event 169 */ mouseReleased(MouseEvent event)170 public void mouseReleased(MouseEvent event) { 171 if (startX > endX) { 172 int tmp = startX; 173 startX = endX; 174 endX = tmp; 175 } 176 mouseReleaseHandler.consume(event); 177 } 178 179 /** 180 * when the mouse Entered event 181 * 182 * @param event event 183 */ mouseEntered(MouseEvent event)184 public void mouseEntered(MouseEvent event) { 185 requestFocusInWindow(); 186 } 187 188 /** 189 * when the mouse Entered event 190 * 191 * @param event event 192 */ mouseExited(MouseEvent event)193 public void mouseExited(MouseEvent event) { 194 Tip.getInstance().hidden(); 195 } 196 197 /** 198 * when the mouse Entered event 199 * 200 * @param event event 201 */ mouseDragged(MouseEvent event)202 public void mouseDragged(MouseEvent event) { 203 Tip.getInstance().hidden(); 204 if (status == Status.DRAG) { 205 endX = event.getX(); 206 } else if (status == Status.LEFT) { 207 startX = event.getX(); 208 } else if (status == Status.RIGHT) { 209 endX = event.getX(); 210 } else if (status == Status.MOVE) { 211 if (event.getX() - offset < 0) { 212 startX = 0; 213 endX = startX + length + event.getX() - offset; 214 } else { 215 startX = event.getX() - offset; 216 if (startX + length > getWidth()) { 217 endX = getWidth(); 218 } else { 219 endX = startX + length; 220 } 221 } 222 } else { 223 endX = endX; 224 } 225 if (startX < 0) { 226 startX = 0; 227 } 228 if (endX < 0) { 229 endX = 0; 230 } 231 if (startX > getWidth()) { 232 startX = getWidth(); 233 } 234 if (endX > getWidth()) { 235 endX = getWidth(); 236 } 237 Utils.setX(selectRect, Math.min(startX, endX)); 238 selectRect.width = Math.abs(endX - startX); 239 setAllRect(); 240 notifyRangeChange(Math.min(startX, endX), Math.max(startX, endX)); 241 } 242 243 /** 244 * when the mouse move event 245 * 246 * @param event event 247 */ mouseMoved(MouseEvent event)248 public void mouseMoved(MouseEvent event) { 249 if (this.getVisibleRect().contains(event.getPoint())) { 250 if (selectLeftRect.contains(event.getPoint())) { 251 setCursor(wCursor); 252 status = Status.LEFT; 253 } else if (selectRightRect.contains(event.getPoint())) { 254 setCursor(eCursor); 255 status = Status.RIGHT; 256 } else if (selectTopRect.contains(event.getPoint())) { 257 setCursor(handCursor); 258 status = Status.MOVE; 259 } else { 260 status = Status.DRAG; 261 setCursor(defaultCursor); 262 tip(event); 263 } 264 } 265 } 266 tip(MouseEvent event)267 private void tip(MouseEvent event) { 268 String timeString = TimeUtils.getTimeFormatString(Common.x2ns(event.getX(), this.getBounds())); 269 Double rate = rateMap.get(event.getX()); 270 if (rate == null) { 271 rate = 0.0d; 272 } 273 List<String> strings = Arrays.asList(timeString, "CPU Usage ", formatter.format(rate), "Select to inspect"); 274 Tip.getInstance().display(this, event.getPoint(), strings); 275 } 276 277 /** 278 * when the key Typed event 279 * 280 * @param event event 281 */ keyTyped(KeyEvent event)282 public void keyTyped(KeyEvent event) { 283 event.getKeyCode(); 284 } 285 286 /** 287 * when the mouse press event 288 * 289 * @param event event 290 */ keyPressed(KeyEvent event)291 public void keyPressed(KeyEvent event) { 292 switch (event.getExtendedKeyCode()) { 293 case VK_A: 294 wheelNS = (endNS - startNS) * -0.2; 295 translation(); 296 break; 297 case VK_D: 298 wheelNS = (endNS - startNS) * 0.2; 299 translation(); 300 break; 301 case VK_W: 302 wheelNS = (endNS - startNS) * 0.2; 303 if (wheelNS == 0) { 304 wheelNS = 50L; 305 } 306 scale(); 307 break; 308 case VK_S: 309 wheelNS = (endNS - startNS) * -0.2; 310 if (wheelNS == 0) { 311 wheelNS = -50L; 312 } 313 scale(); 314 break; 315 default: 316 break; 317 } 318 } 319 320 /** 321 * on the key released 322 * 323 * @param event event 324 */ keyReleased(KeyEvent event)325 public void keyReleased(KeyEvent event) { 326 if (event.getExtendedKeyCode() == VK_A || event.getExtendedKeyCode() == VK_S 327 || event.getExtendedKeyCode() == VK_D || event.getExtendedKeyCode() == VK_W) { 328 keyReleaseHandler.consume(event); 329 } 330 } 331 332 /** 333 * put Rate Map 334 * 335 * @param xPoint xPoint 336 * @param rate rate 337 */ putRateMap(int xPoint, double rate)338 public void putRateMap(int xPoint, double rate) { 339 if (rateMap.containsKey(xPoint)) { 340 if (rateMap.get(xPoint) == 0d) { 341 rateMap.put(xPoint, rate); 342 } 343 } else { 344 rateMap.put(xPoint, rate); 345 } 346 } 347 348 /** 349 * clear map 350 */ clearMap()351 public void clearMap() { 352 rateMap.clear(); 353 } 354 355 /** 356 * set the current range 357 * 358 * @param mStartNS mStartNS 359 * @param mEndNS mEndNS 360 */ setRange(long mStartNS, long mEndNS)361 public void setRange(long mStartNS, long mEndNS) { 362 startX = (int) (mStartNS * getWidth() * 1.0 / DURATION); 363 endX = (int) (mEndNS * getWidth() * 1.0 / DURATION); 364 double l20 = (mEndNS - mStartNS) * 1.0 / 20; 365 long min; 366 long max; 367 for (int index = 0; index < scales.length; index++) { 368 if (scales[index] > l20) { 369 if (index > 0) { 370 min = scales[index - 1]; 371 } else { 372 min = 0; 373 } 374 max = scales[index]; 375 double weight = (l20 - min) * 1.0 / (max - min); 376 if (weight > 0.243) { 377 scale = max; 378 } else { 379 scale = min; 380 } 381 break; 382 } 383 } 384 if (scale == 0) { 385 scale = scales[0]; 386 } 387 if (startX == 0 && endX == 0) { 388 endX = 1; 389 } 390 Utils.setX(selectRect, Math.min(startX, endX)); 391 selectRect.width = Math.abs(endX - startX); 392 setAllRect(); 393 } 394 395 @Override paintComponent(Graphics graphics)396 public void paintComponent(Graphics graphics) { 397 super.paintComponent(graphics); 398 if (graphics instanceof Graphics2D) { 399 Graphics2D g2 = (Graphics2D) graphics; 400 Optional.ofNullable(timeShaftConsumer).ifPresent(it -> it.consume(g2)); 401 g2.setColor(JBColor.background().darker()); 402 g2.setComposite(alpha40); 403 g2.fillRect(0, 0, getWidth(), getHeight()); 404 g2.setComposite(alpha100); 405 g2.setColor(JBColor.foreground()); 406 g2.drawString("CPU Usage", 3, 13); 407 g2.setComposite(alpha100); 408 if (startX == 0 && endX == 0) { 409 startX = 0; 410 endX = getWidth(); 411 Utils.setX(selectRect, 0); 412 Utils.setY(selectRect, 0); 413 selectRect.width = getWidth(); 414 selectRect.height = getHeight(); 415 setAllRect(); 416 } 417 g2.setColor(JBUI.CurrentTheme.Link.linkColor()); 418 g2.setComposite(alpha20); 419 g2.fillRect(Utils.getX(selectRect), Utils.getY(selectRect), selectRect.width, selectRect.height); 420 g2.setComposite(alpha40); 421 g2.fillRect(Utils.getX(selectTopRect), Utils.getY(selectTopRect), selectTopRect.width, 422 selectTopRect.height); 423 g2.setComposite(alpha100); 424 g2.fillRect(Utils.getX(selectLeftRect), Utils.getY(selectLeftRect), selectLeftRect.width, 425 selectLeftRect.height); 426 g2.fillRect(Utils.getX(selectRightRect), Utils.getY(selectRightRect), selectRightRect.width, 427 selectRightRect.height); 428 if (!isInit) { 429 isInit = true; 430 setRange(0L, DURATION); 431 notifyRangeChange(0L, DURATION); 432 } 433 } 434 } 435 setAllRect()436 private void setAllRect() { 437 ratio1 = startX * 1.0 / getWidth(); 438 if (ratio1 < 0) { 439 ratio1 = 0; 440 } 441 ratio2 = endX * 1.0 / getWidth(); 442 if (ratio2 > 1) { 443 ratio2 = 1; 444 } 445 Utils.setX(selectTopRect, Utils.getX(selectRect)); 446 Utils.setY(selectTopRect, Utils.getY(selectRect)); 447 selectTopRect.width = selectRect.width; 448 selectTopRect.height = SELECT_BORDER_WIDTH * 5; 449 Utils.setX(selectLeftRect, Utils.getX(selectRect)); 450 Utils.setY(selectLeftRect, Utils.getY(selectRect)); 451 selectLeftRect.width = SELECT_BORDER_WIDTH; 452 selectLeftRect.height = selectRect.height; 453 Utils.setX(selectRightRect, Utils.getX(selectRect) + selectRect.width - SELECT_BORDER_WIDTH); 454 Utils.setY(selectRightRect, Utils.getY(selectRect)); 455 selectRightRect.width = selectLeftRect.width; 456 selectRightRect.height = selectRect.height; 457 } 458 translation()459 private void translation() { 460 if (startNS + wheelNS <= 0) { 461 startNS = 0; 462 endNS = endNS - startNS; 463 setRange(startNS, endNS); 464 } else if (endNS + wheelNS >= DURATION) { 465 startNS = DURATION - (endNS - startNS); 466 endNS = DURATION; 467 setRange(startNS, endNS); 468 } else { 469 startNS = (long) (startNS + wheelNS); 470 endNS = (long) (endNS + wheelNS); 471 setRange(startNS, endNS); 472 } 473 notifyRangeChange(startNS, endNS); 474 } 475 scale()476 private void scale() { 477 startNS = (long) (startNS + wheelNS); 478 endNS = (long) (endNS - wheelNS); 479 if (startNS <= 0) { 480 startNS = 0L; 481 } 482 if (endNS >= DURATION) { 483 endNS = DURATION; 484 } 485 setRange(startNS, endNS); 486 notifyRangeChange(startNS, endNS); 487 } 488 notifyRangeChange(int xPoint, int yPoint)489 private void notifyRangeChange(int xPoint, int yPoint) { 490 long ns1 = Common.x2ns(xPoint, getVisibleRect()); 491 long ns2 = Common.x2ns(yPoint, getVisibleRect()); 492 startNS = Math.min(ns1, ns2); 493 endNS = Math.max(ns1, ns2); 494 repaint(); 495 Optional.ofNullable(rangeListener).ifPresent(range -> range.change(startNS, endNS, scale)); 496 } 497 notifyRangeChange(long ns1, long ns2)498 private void notifyRangeChange(long ns1, long ns2) { 499 startNS = Math.min(ns1, ns2); 500 endNS = Math.max(ns1, ns2); 501 repaint(); 502 Optional.ofNullable(rangeListener).ifPresent(range -> range.change(startNS, endNS, scale)); 503 } 504 505 enum Status { 506 DRAG, LEFT, RIGHT, MOVE 507 } 508 } 509