All of lore.kernel.org
 help / color / mirror / Atom feed
From: Arnaldo Carvalho de Melo <arnaldo.melo@gmail.com>
To: Adrian Hunter <adrian.hunter@intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>,
	linux-kernel@vger.kernel.org, linux-perf-users@vger.kernel.org
Subject: Re: [PATCH 4/6] perf scripts python: exported-sql-viewer.py: Add copy to clipboard
Date: Mon, 13 May 2019 17:03:22 -0300	[thread overview]
Message-ID: <20190513200322.GD3198@kernel.org> (raw)
In-Reply-To: <20190503120828.25326-5-adrian.hunter@intel.com>

Em Fri, May 03, 2019 at 03:08:26PM +0300, Adrian Hunter escreveu:
> Add support for copying to clipboard. Two menu options are added to copy the
> selected rows / columns with normal spacing, or as comma-separated-values.
> In the case of trees, only entire rows can be copied.

Cool, it works:

    Comitter testing:

      $ python ~acme/libexec/perf-core/scripts/python/exported-sql-viewer.py ~/c/adrian.hunter/simple-retpoline.db

    Select the lines, press control+C and on the same terminal,
    press control+shift+V and voilà:

    Call Path                           Object           Count  Time (ns)  Time (%)  Branch Count  Branch Count (%)
      ▼ 14503:14503
        ▼ _start                        ld-2.28.so           1     156267     100.0         10602             100.0
            unknown                     unknown              1       2276       1.5             1               0.0
          ▼ _dl_start                   ld-2.28.so           1     137047      87.7         10088              95.2
            ▶ unknown                   unknown              4       4127       3.0             4               0.0
              _dl_setup_hash            ld-2.28.so           1          0       0.0             1               0.0
            ▶ _dl_sysdep_start          ld-2.28.so           1     131342      95.8          9981              98.9
          ▼ _dl_init                    ld-2.28.so           1       9142       5.9           326               3.1
            ▼ call_init.part.0          ld-2.28.so           3       9133      99.9           319              97.9
              ▶ _init                   libc-2.28.so         1       6877      75.3           110              34.5
              ▶ check_stdfiles_vtables  libc-2.28.so         1         76       0.8             2               0.6
              ▶ init_cacheinfo          libc-2.28.so         1       1991      21.8           197              61.8
          ▶ _start                      simple-retpoline     1       7457       4.8           182               1.7

 
> Signed-off-by: Adrian Hunter <adrian.hunter@intel.com>
> ---
>  .../scripts/python/exported-sql-viewer.py     | 217 ++++++++++++++++++
>  1 file changed, 217 insertions(+)
> 
> diff --git a/tools/perf/scripts/python/exported-sql-viewer.py b/tools/perf/scripts/python/exported-sql-viewer.py
> index c0fb88d440ba..5804d9705ab7 100755
> --- a/tools/perf/scripts/python/exported-sql-viewer.py
> +++ b/tools/perf/scripts/python/exported-sql-viewer.py
> @@ -898,6 +898,8 @@ class TreeWindowBase(QMdiSubWindow):
>  		self.find_bar = None
>  
>  		self.view = QTreeView()
> +		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
> +		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
>  
>  	def DisplayFound(self, ids):
>  		if not len(ids):
> @@ -1666,6 +1668,8 @@ class BranchWindow(QMdiSubWindow):
>  
>  		self.view = QTreeView()
>  		self.view.setUniformRowHeights(True)
> +		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
> +		self.view.CopyCellsToClipboard = CopyTreeCellsToClipboard
>  		self.view.setModel(self.model)
>  
>  		self.ResizeColumnsToContents()
> @@ -2278,6 +2282,207 @@ class ResizeColumnsToContentsBase(QObject):
>  		self.data_model.rowsInserted.disconnect(self.UpdateColumnWidths)
>  		self.ResizeColumnsToContents()
>  
> +# Convert value to CSV
> +
> +def ToCSValue(val):
> +	if '"' in val:
> +		val = val.replace('"', '""')
> +	if "," in val or '"' in val:
> +		val = '"' + val + '"'
> +	return val
> +
> +# Key to sort table model indexes by row / column, assuming fewer than 1000 columns
> +
> +glb_max_cols = 1000
> +
> +def RowColumnKey(a):
> +	return a.row() * glb_max_cols + a.column()
> +
> +# Copy selected table cells to clipboard
> +
> +def CopyTableCellsToClipboard(view, as_csv=False, with_hdr=False):
> +	indexes = sorted(view.selectedIndexes(), key=RowColumnKey)
> +	idx_cnt = len(indexes)
> +	if not idx_cnt:
> +		return
> +	if idx_cnt == 1:
> +		with_hdr=False
> +	min_row = indexes[0].row()
> +	max_row = indexes[0].row()
> +	min_col = indexes[0].column()
> +	max_col = indexes[0].column()
> +	for i in indexes:
> +		min_row = min(min_row, i.row())
> +		max_row = max(max_row, i.row())
> +		min_col = min(min_col, i.column())
> +		max_col = max(max_col, i.column())
> +	if max_col > glb_max_cols:
> +		raise RuntimeError("glb_max_cols is too low")
> +	max_width = [0] * (1 + max_col - min_col)
> +	for i in indexes:
> +		c = i.column() - min_col
> +		max_width[c] = max(max_width[c], len(str(i.data())))
> +	text = ""
> +	pad = ""
> +	sep = ""
> +	if with_hdr:
> +		model = indexes[0].model()
> +		for col in range(min_col, max_col + 1):
> +			val = model.headerData(col, Qt.Horizontal)
> +			if as_csv:
> +				text += sep + ToCSValue(val)
> +				sep = ","
> +			else:
> +				c = col - min_col
> +				max_width[c] = max(max_width[c], len(val))
> +				width = max_width[c]
> +				align = model.headerData(col, Qt.Horizontal, Qt.TextAlignmentRole)
> +				if align & Qt.AlignRight:
> +					val = val.rjust(width)
> +				text += pad + sep + val
> +				pad = " " * (width - len(val))
> +				sep = "  "
> +		text += "\n"
> +		pad = ""
> +		sep = ""
> +	last_row = min_row
> +	for i in indexes:
> +		if i.row() > last_row:
> +			last_row = i.row()
> +			text += "\n"
> +			pad = ""
> +			sep = ""
> +		if as_csv:
> +			text += sep + ToCSValue(str(i.data()))
> +			sep = ","
> +		else:
> +			width = max_width[i.column() - min_col]
> +			if i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
> +				val = str(i.data()).rjust(width)
> +			else:
> +				val = str(i.data())
> +			text += pad + sep + val
> +			pad = " " * (width - len(val))
> +			sep = "  "
> +	QApplication.clipboard().setText(text)
> +
> +def CopyTreeCellsToClipboard(view, as_csv=False, with_hdr=False):
> +	indexes = view.selectedIndexes()
> +	if not len(indexes):
> +		return
> +
> +	selection = view.selectionModel()
> +
> +	first = None
> +	for i in indexes:
> +		above = view.indexAbove(i)
> +		if not selection.isSelected(above):
> +			first = i
> +			break
> +
> +	if first is None:
> +		raise RuntimeError("CopyTreeCellsToClipboard internal error")
> +
> +	model = first.model()
> +	row_cnt = 0
> +	col_cnt = model.columnCount(first)
> +	max_width = [0] * col_cnt
> +
> +	indent_sz = 2
> +	indent_str = " " * indent_sz
> +
> +	expanded_mark_sz = 2
> +	if sys.version_info[0] == 3:
> +		expanded_mark = "\u25BC "
> +		not_expanded_mark = "\u25B6 "
> +	else:
> +		expanded_mark = unicode(chr(0xE2) + chr(0x96) + chr(0xBC) + " ", "utf-8")
> +		not_expanded_mark =  unicode(chr(0xE2) + chr(0x96) + chr(0xB6) + " ", "utf-8")
> +	leaf_mark = "  "
> +
> +	if not as_csv:
> +		pos = first
> +		while True:
> +			row_cnt += 1
> +			row = pos.row()
> +			for c in range(col_cnt):
> +				i = pos.sibling(row, c)
> +				if c:
> +					n = len(str(i.data()))
> +				else:
> +					n = len(str(i.data()).strip())
> +					n += (i.internalPointer().level - 1) * indent_sz
> +					n += expanded_mark_sz
> +				max_width[c] = max(max_width[c], n)
> +			pos = view.indexBelow(pos)
> +			if not selection.isSelected(pos):
> +				break
> +
> +	text = ""
> +	pad = ""
> +	sep = ""
> +	if with_hdr:
> +		for c in range(col_cnt):
> +			val = model.headerData(c, Qt.Horizontal, Qt.DisplayRole).strip()
> +			if as_csv:
> +				text += sep + ToCSValue(val)
> +				sep = ","
> +			else:
> +				max_width[c] = max(max_width[c], len(val))
> +				width = max_width[c]
> +				align = model.headerData(c, Qt.Horizontal, Qt.TextAlignmentRole)
> +				if align & Qt.AlignRight:
> +					val = val.rjust(width)
> +				text += pad + sep + val
> +				pad = " " * (width - len(val))
> +				sep = "   "
> +		text += "\n"
> +		pad = ""
> +		sep = ""
> +
> +	pos = first
> +	while True:
> +		row = pos.row()
> +		for c in range(col_cnt):
> +			i = pos.sibling(row, c)
> +			val = str(i.data())
> +			if not c:
> +				if model.hasChildren(i):
> +					if view.isExpanded(i):
> +						mark = expanded_mark
> +					else:
> +						mark = not_expanded_mark
> +				else:
> +					mark = leaf_mark
> +				val = indent_str * (i.internalPointer().level - 1) + mark + val.strip()
> +			if as_csv:
> +				text += sep + ToCSValue(val)
> +				sep = ","
> +			else:
> +				width = max_width[c]
> +				if c and i.data(Qt.TextAlignmentRole) & Qt.AlignRight:
> +					val = val.rjust(width)
> +				text += pad + sep + val
> +				pad = " " * (width - len(val))
> +				sep = "   "
> +		pos = view.indexBelow(pos)
> +		if not selection.isSelected(pos):
> +			break
> +		text = text.rstrip() + "\n"
> +		pad = ""
> +		sep = ""
> +
> +	QApplication.clipboard().setText(text)
> +
> +def CopyCellsToClipboard(view, as_csv=False, with_hdr=False):
> +	view.CopyCellsToClipboard(view, as_csv, with_hdr)
> +
> +def CopyCellsToClipboardHdr(view):
> +	CopyCellsToClipboard(view, False, True)
> +
> +def CopyCellsToClipboardCSV(view):
> +	CopyCellsToClipboard(view, True, True)
> +
>  # Table window
>  
>  class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
> @@ -2296,6 +2501,8 @@ class TableWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
>  		self.view.verticalHeader().setVisible(False)
>  		self.view.sortByColumn(-1, Qt.AscendingOrder)
>  		self.view.setSortingEnabled(True)
> +		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
> +		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
>  
>  		self.ResizeColumnsToContents()
>  
> @@ -2412,6 +2619,8 @@ class TopCallsWindow(QMdiSubWindow, ResizeColumnsToContentsBase):
>  		self.view.setModel(self.model)
>  		self.view.setEditTriggers(QAbstractItemView.NoEditTriggers)
>  		self.view.verticalHeader().setVisible(False)
> +		self.view.setSelectionMode(QAbstractItemView.ContiguousSelection)
> +		self.view.CopyCellsToClipboard = CopyTableCellsToClipboard
>  
>  		self.ResizeColumnsToContents()
>  
> @@ -2749,6 +2958,8 @@ class MainWindow(QMainWindow):
>  		file_menu.addAction(CreateExitAction(glb.app, self))
>  
>  		edit_menu = menu.addMenu("&Edit")
> +		edit_menu.addAction(CreateAction("&Copy", "Copy to clipboard", self.CopyToClipboard, self, QKeySequence.Copy))
> +		edit_menu.addAction(CreateAction("Copy as CS&V", "Copy to clipboard as CSV", self.CopyToClipboardCSV, self))
>  		edit_menu.addAction(CreateAction("&Find...", "Find items", self.Find, self, QKeySequence.Find))
>  		edit_menu.addAction(CreateAction("Fetch &more records...", "Fetch more records", self.FetchMoreRecords, self, [QKeySequence(Qt.Key_F8)]))
>  		edit_menu.addAction(CreateAction("&Shrink Font", "Make text smaller", self.ShrinkFont, self, [QKeySequence("Ctrl+-")]))
> @@ -2781,6 +2992,12 @@ class MainWindow(QMainWindow):
>  			except:
>  				pass
>  
> +	def CopyToClipboard(self):
> +		self.Try(CopyCellsToClipboardHdr)
> +
> +	def CopyToClipboardCSV(self):
> +		self.Try(CopyCellsToClipboardCSV)
> +
>  	def Find(self):
>  		win = self.mdi_area.activeSubWindow()
>  		if win:
> -- 
> 2.17.1

-- 

- Arnaldo

  reply	other threads:[~2019-05-13 20:03 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-05-03 12:08 [PATCH 0/6] perf scripts python: exported-sql-viewer.py: Minor improvements Adrian Hunter
2019-05-03 12:08 ` [PATCH 1/6] perf scripts python: exported-sql-viewer.py: Fix error when shrinking / enlarging font Adrian Hunter
2019-05-13 19:57   ` Arnaldo Carvalho de Melo
2019-05-18  8:55   ` [tip:perf/core] " tip-bot for Adrian Hunter
2019-05-03 12:08 ` [PATCH 2/6] perf scripts python: exported-sql-viewer.py: Move view creation Adrian Hunter
2019-05-18  8:54   ` [tip:perf/core] " tip-bot for Adrian Hunter
2019-05-03 12:08 ` [PATCH 3/6] perf scripts python: exported-sql-viewer.py: Add tree level Adrian Hunter
2019-05-18  8:55   ` [tip:perf/core] " tip-bot for Adrian Hunter
2019-05-03 12:08 ` [PATCH 4/6] perf scripts python: exported-sql-viewer.py: Add copy to clipboard Adrian Hunter
2019-05-13 20:03   ` Arnaldo Carvalho de Melo [this message]
2019-05-18  8:56   ` [tip:perf/core] " tip-bot for Adrian Hunter
2019-05-03 12:08 ` [PATCH 5/6] perf scripts python: exported-sql-viewer.py: Add context menu Adrian Hunter
2019-05-13 20:09   ` Arnaldo Carvalho de Melo
2019-05-18  8:57   ` [tip:perf/core] " tip-bot for Adrian Hunter
2019-05-03 12:08 ` [PATCH 6/6] perf scripts python: exported-sql-viewer.py: Add 'About' dialog box Adrian Hunter
2019-05-13 20:12   ` Arnaldo Carvalho de Melo
2019-05-18  8:58   ` [tip:perf/core] " tip-bot for Adrian Hunter

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190513200322.GD3198@kernel.org \
    --to=arnaldo.melo@gmail.com \
    --cc=adrian.hunter@intel.com \
    --cc=jolsa@redhat.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-perf-users@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.