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.charts.tooltip; 17 18 import com.intellij.ui.JBColor; 19 import com.intellij.ui.components.JBLabel; 20 import ohos.devtools.views.charts.model.ChartDataModel; 21 import ohos.devtools.views.charts.utils.ChartUtils; 22 import org.apache.commons.lang3.StringUtils; 23 import org.apache.logging.log4j.LogManager; 24 import org.apache.logging.log4j.Logger; 25 26 import javax.swing.JComponent; 27 import javax.swing.JLayeredPane; 28 import javax.swing.JPanel; 29 import javax.swing.JRootPane; 30 import javax.swing.SwingUtilities; 31 import javax.swing.border.EmptyBorder; 32 import javax.swing.border.LineBorder; 33 import java.awt.Color; 34 import java.awt.Dimension; 35 import java.awt.FlowLayout; 36 import java.awt.GridLayout; 37 import java.awt.Point; 38 import java.awt.event.MouseEvent; 39 import java.util.List; 40 import java.util.regex.Pattern; 41 42 /** 43 * Customize the tooltip that can follow the mouse as a legend 44 * 45 * @since 2021/5/19 16:39 46 */ 47 public final class LegendTooltip extends JComponent { 48 private static final Logger LOGGER = LogManager.getLogger(LegendTooltip.class); 49 50 private static final int MIN_SIZE = 2; 51 52 private static final int NUM_2 = 2; 53 54 private static final int NUM_3 = 4; 55 56 /** 57 * 常量1,距离鼠标常量,防止鼠标遮挡 58 */ 59 private static final Point CONST_POINT = new Point(12, 5); 60 61 /** 62 * 常量2,防止离鼠标太近,触发mouse exit事件 63 */ 64 private static final Point CONST_POINT2 = new Point(30, 75); 65 66 /** 67 * Grid布局的默认行数 68 */ 69 private static final int DEFAULT_ROWS = 2; 70 71 /** 72 * Grid布局的行高 73 */ 74 private static final int ROW_HEIGHT = 36; 75 76 /** 77 * Tooltip默认宽度 78 */ 79 private static final int DEFAULT_WIDTH = 170; 80 81 /** 82 * thread unspecified 83 */ 84 private static final int THREAD_UNSPECIFIED = 0; 85 86 /** 87 * thread running 88 */ 89 private static final int THREAD_RUNNING = 1; 90 91 /** 92 * thread sleeping 93 */ 94 private static final int THREAD_SLEEPING = 2; 95 96 /** 97 * thread stopped 98 */ 99 private static final int THREAD_STOPPED = 3; 100 101 /** 102 * thread waiting 103 */ 104 private static final int THREAD_WAITING = 4; 105 106 /** 107 * 当前Tooltip的主面板 108 */ 109 private JPanel mainPanel; 110 111 /** 112 * 当前Tooltip的父组件的根面板 113 */ 114 private JRootPane parentRootPane; 115 116 /** 117 * 窗口的遮罩层,不能随便修改,只作父对象应用 118 */ 119 private JLayeredPane mask; 120 121 /** 122 * 当前Tooltip中Grid布局的行数 123 */ 124 private int rows = DEFAULT_ROWS; 125 126 /** 127 * Constructor 128 */ LegendTooltip()129 public LegendTooltip() { 130 initTip(); 131 } 132 133 /** 134 * 初始化Tooltip 135 */ initTip()136 private void initTip() { 137 this.setLayout(new FlowLayout()); 138 // false为控件透明,true为不透明 139 this.setOpaque(false); 140 this.setVisible(false); 141 mainPanel = new JPanel(new GridLayout(rows, 1)); 142 mainPanel.setBackground(JBColor.background().darker()); 143 } 144 145 /** 146 * 隐藏组件 147 */ hideTip()148 public void hideTip() { 149 this.setVisible(false); 150 } 151 152 /** 153 * 为某个组件设置tip 154 * 155 * @param parent 显示tooltip的对象 156 * @param timeline 显示的时间Tip 157 * @param totalValue Total值 158 * @param tooltipItems 要显示的图例 159 * @param isCharting boolean 160 * @param axisYUnit axisYUnit 161 */ showTip(JComponent parent, String timeline, String totalValue, List<TooltipItem> tooltipItems, boolean isCharting, String axisYUnit)162 public void showTip(JComponent parent, String timeline, String totalValue, List<TooltipItem> tooltipItems, 163 boolean isCharting, String axisYUnit) { 164 if (parent != null && parent.getRootPane() != null) { 165 this.rows = tooltipItems.size() + NUM_2; 166 // 重新组建Tooltip 167 if (isCharting) { 168 rebuild(parent); 169 resize(); 170 return; 171 } 172 173 // 动态添加和绘制图例 174 addLegends(timeline, totalValue, tooltipItems, axisYUnit); 175 this.validate(); 176 this.setVisible(true); 177 } 178 } 179 180 /** 181 * 重新组建Tooltip 182 * 183 * @param parent 显示tooltip的对象 184 */ rebuild(JComponent parent)185 private void rebuild(JComponent parent) { 186 parentRootPane = parent.getRootPane(); 187 JLayeredPane layerPane = parentRootPane.getLayeredPane(); 188 189 // 先从旧面板中移除tip 190 if (mask != null && mask != layerPane) { 191 mask.remove(this); 192 } 193 mask = layerPane; 194 195 // 防止还有没有移除监听的组件 196 layerPane.remove(this); 197 198 // 由于每次要重绘mainPanel,所以也要先移除mainPanel 199 this.remove(mainPanel); 200 201 // 放置tip在遮罩窗口顶层 202 layerPane.add(this, JLayeredPane.POPUP_LAYER); 203 // 窗口遮罩层添加监听 204 205 // 根据传入的TooltipItem集合大小,重新创建mainPanel 206 mainPanel = new JPanel(new GridLayout(rows, 1)); 207 mainPanel.setBorder(new LineBorder(Color.BLACK)); 208 mainPanel.setBackground(JBColor.background().darker()); 209 this.add(mainPanel); 210 } 211 212 /** 213 * Tooltip中添加图例 214 * 215 * @param timeline 时间 216 * @param totalValue Total值 217 * @param tooltipItems 图例集合 218 * @param axisYUnit axisYUnit 219 */ addLegends(String timeline, String totalValue, List<TooltipItem> tooltipItems, String axisYUnit)220 private void addLegends(String timeline, String totalValue, List<TooltipItem> tooltipItems, String axisYUnit) { 221 mainPanel.removeAll(); 222 // 添加时间 223 JBLabel timeLabel = new JBLabel(); 224 timeLabel.setBorder(new EmptyBorder(5, 5, 5, 5)); 225 long ms = 0L; 226 String pattern = "^\\d{0,20}$"; 227 boolean isMatch = Pattern.matches(pattern, timeline); 228 if (isMatch) { 229 ms = Long.parseLong(timeline); 230 } else { 231 LOGGER.error("Time format error:{}", timeline); 232 } 233 timeLabel.setText(ChartUtils.formatTime(ms)); 234 timeLabel.setOpaque(false); 235 mainPanel.add(timeLabel); 236 if (StringUtils.isNotBlank(totalValue)) { 237 // 添加悬浮框的total值 238 JBLabel totalLabel = new JBLabel(); 239 totalLabel.setBorder(new EmptyBorder(0, 5, 5, 5)); 240 totalLabel.setOpaque(false); 241 totalLabel.setText("Total:" + totalValue + axisYUnit); 242 mainPanel.add(totalLabel); 243 } 244 // 添加图例 245 for (TooltipItem tooltipItem : tooltipItems) { 246 JPanel single = new JPanel(new FlowLayout(FlowLayout.LEFT)); 247 single.setOpaque(false); 248 249 Color color = tooltipItem.getColor(); 250 if (color != null) { 251 single.add(new TooltipColorRect(tooltipItem.getColor())); 252 } 253 JBLabel nameLabel = new JBLabel(); 254 nameLabel.setOpaque(false); 255 nameLabel.setText(tooltipItem.getText()); 256 single.add(nameLabel); 257 mainPanel.add(single); 258 } 259 } 260 261 /** 262 * 坐标转换,标签跟随鼠标移动 263 * 264 * @param mouseEvent MouseEvent 265 */ followWithMouse(MouseEvent mouseEvent)266 public void followWithMouse(MouseEvent mouseEvent) { 267 if (mask == null) { 268 return; 269 } 270 271 if (this.getWidth() < MIN_SIZE || this.getHeight() < MIN_SIZE) { 272 this.setVisible(false); 273 return; 274 } 275 276 this.setVisible(true); 277 Point screenPoint = mouseEvent.getLocationOnScreen(); 278 SwingUtilities.convertPointFromScreen(screenPoint, mask); 279 280 int newLocationX = (int) (screenPoint.getX() + CONST_POINT.getX()); 281 int newLocationY = (int) (screenPoint.getY() + CONST_POINT.getY()); 282 283 Dimension tipSize = mainPanel.getPreferredSize(); 284 if (newLocationX + tipSize.width > parentRootPane.getWidth()) { 285 newLocationX = (int) (screenPoint.getX() - tipSize.width - CONST_POINT2.getX()); 286 } 287 if (newLocationY + tipSize.height > parentRootPane.getHeight()) { 288 newLocationY = (int) (screenPoint.getY() - tipSize.height - CONST_POINT2.getY()); 289 } 290 291 this.setLocation(newLocationX, newLocationY); 292 } 293 294 /** 295 * 重新调整大小 296 */ resize()297 private void resize() { 298 this.setSize(DEFAULT_WIDTH, this.rows * ROW_HEIGHT); 299 } 300 301 /** 302 * showThreadStatusTip 303 * 304 * @param parent parent 305 * @param timeline timeline 306 * @param chartDataModel chartDataModel 307 * @param isCharting isCharting 308 */ showThreadStatusTip(JComponent parent, String timeline, ChartDataModel chartDataModel, boolean isCharting)309 public void showThreadStatusTip(JComponent parent, String timeline, ChartDataModel chartDataModel, 310 boolean isCharting) { 311 if (parent != null && parent.getRootPane() != null) { 312 if (isCharting) { 313 this.rows = NUM_3; 314 rebuild(parent); 315 resize(); 316 return; 317 } 318 addThreadStatusLegends(timeline, chartDataModel.getName(), chartDataModel.getValue(), 319 chartDataModel.getDoubleValue()); 320 this.validate(); 321 this.setVisible(true); 322 } 323 } 324 325 /** 326 * addThreadStatusLegends 327 * 328 * @param timeline timeline 329 * @param threadName threadName 330 * @param threadStatus threadStatus 331 * @param threadUsage threadUsage 332 */ addThreadStatusLegends(String timeline, String threadName, int threadStatus, double threadUsage)333 private void addThreadStatusLegends(String timeline, String threadName, int threadStatus, double threadUsage) { 334 mainPanel.removeAll(); 335 // 添加时间 336 JBLabel timeLabel = new JBLabel(); 337 timeLabel.setBorder(new EmptyBorder(5, 5, 5, 5)); 338 long ms = 0L; 339 String pattern = "^\\d{0,20}$"; 340 boolean isMatch = Pattern.matches(pattern, timeline); 341 if (isMatch) { 342 ms = Long.parseLong(timeline); 343 } else { 344 LOGGER.error("Time format error:{}", timeline); 345 } 346 timeLabel.setText(ChartUtils.formatTime(ms)); 347 timeLabel.setOpaque(false); 348 mainPanel.add(timeLabel); 349 350 JBLabel nameLabel = new JBLabel(); 351 nameLabel.setBorder(new EmptyBorder(0, 5, 5, 5)); 352 nameLabel.setOpaque(false); 353 nameLabel.setText("Thread:" + threadName); 354 mainPanel.add(nameLabel); 355 356 JBLabel statusLabel = new JBLabel(); 357 statusLabel.setBorder(new EmptyBorder(0, 5, 5, 5)); 358 statusLabel.setOpaque(false); 359 switch (threadStatus) { 360 case THREAD_RUNNING: 361 statusLabel.setText("RUNNING"); 362 break; 363 case THREAD_SLEEPING: 364 statusLabel.setText("SLEEPING"); 365 break; 366 case THREAD_STOPPED: 367 statusLabel.setText("STOPPED"); 368 break; 369 case THREAD_WAITING: 370 statusLabel.setText("WAITING"); 371 break; 372 default: 373 statusLabel.setText("UNSPECIFIED"); 374 } 375 mainPanel.add(statusLabel); 376 JBLabel usageLabel = new JBLabel(); 377 usageLabel.setBorder(new EmptyBorder(0, 5, 5, 5)); 378 usageLabel.setOpaque(false); 379 usageLabel.setText("OccRate:" + threadUsage); 380 mainPanel.add(usageLabel); 381 } 382 383 /** 384 * showGpuTip 385 * 386 * @param parent parent 387 * @param timeline timeline 388 * @param tooltipItems tooltipItems 389 * @param isCharting isCharting 390 */ showGpuTip(JComponent parent, String timeline, List<TooltipItem> tooltipItems, boolean isCharting)391 public void showGpuTip(JComponent parent, String timeline, List<TooltipItem> tooltipItems, boolean isCharting) { 392 if (parent != null && parent.getRootPane() != null) { 393 this.rows = tooltipItems.size() + 1; 394 if (isCharting) { 395 rebuild(parent); 396 this.setSize(300, this.rows * ROW_HEIGHT); 397 return; 398 } 399 400 addGpuLegends(timeline, tooltipItems); 401 this.validate(); 402 this.setVisible(true); 403 } 404 } 405 addGpuLegends(String timeline, List<TooltipItem> tooltipItems)406 private void addGpuLegends(String timeline, List<TooltipItem> tooltipItems) { 407 mainPanel.removeAll(); 408 JBLabel timeLabel = new JBLabel(); 409 timeLabel.setBorder(new EmptyBorder(5, 5, 5, 5)); 410 long ms = 0L; 411 String pattern = "^\\d{0,20}$"; 412 boolean isMatch = Pattern.matches(pattern, timeline); 413 if (isMatch) { 414 ms = Long.parseLong(timeline); 415 } else { 416 LOGGER.error("Time format error:{}", timeline); 417 } 418 timeLabel.setText(ChartUtils.formatTime(ms)); 419 timeLabel.setOpaque(false); 420 mainPanel.add(timeLabel); 421 for (TooltipItem tooltipItem : tooltipItems) { 422 JPanel single = new JPanel(new FlowLayout(FlowLayout.LEFT)); 423 single.setOpaque(false); 424 JBLabel nameLabel = new JBLabel(); 425 nameLabel.setOpaque(false); 426 nameLabel.setText(tooltipItem.getName() + " : " + tooltipItem.getText()); 427 single.add(nameLabel); 428 mainPanel.add(single); 429 } 430 } 431 } 432