• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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