/*
* Copyright (C) 2013 ~ 2018 National University of Defense Technology(NUDT) & Tianjin Kylin Ltd.
*
* Authors:
* Kobe Lee xiangli@ubuntukylin.com/kobe24_lixiang@126.com
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "filesystemlistwidget.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
FileSystemListWidget::FileSystemListWidget(QList toBeDisplayedColumns, QWidget *parent) : QWidget(parent)
,m_titlePadding(10)
,m_titleHeight(34)
,m_rowHeight(29)
,m_offSet(0)
,m_origOffset(0)
,m_scrollbarWidth(10)
,m_titleHoverColumn(-1)
,m_titlePressColumn(-1)
,m_mouseAtScrollArea(false)
,m_mouseDragScrollbar(false)
{
this->m_lastItem = NULL;
this->m_listItems = new QList();
this->m_selectedItems = new QList();
this->columnTitles << tr("Device") << tr("Directory") << tr("Type") << tr("Total") << tr("Free") << tr("Available") << tr("Used");
QList widths;
widths << 150 << -1 << 60 << 60 << 60 << 60 << 180;//-1时让改行填充所有剩余空间
QFont font;
font.setPixelSize(12);//需要和填充所有剩余空间的那个的文字字体大小一致 font.setPointSize(9)
QFontMetrics fm(font);
this->m_columnWidths.clear();
for (int i = 0; i < widths.length(); i++) {
if (widths[i] == -1) {
this->m_columnWidths << widths[i];
} else {//-1时让改行填充所有剩余空间
int maxWidth = fm.width(this->columnTitles[i]) + this->m_titlePadding + /*m_upArrowPixmap.width() / m_upArrowPixmap.devicePixelRatio() +*/ 2 * 2;
this->m_columnWidths << std::max(widths[i], maxWidth);
}
}
this->m_columnVisibles.clear();
for (int i = 0; i < toBeDisplayedColumns.count(); i++) {
this->m_columnVisibles.append(toBeDisplayedColumns[i]);
}
this->setFocus();
}
FileSystemListWidget::~FileSystemListWidget()
{
if (this->m_hideScrollbarTimer != NULL) {
disconnect(this->m_hideScrollbarTimer,SIGNAL(timeout()),this,SLOT(hideScrollbar()));
if(this->m_hideScrollbarTimer->isActive()) {
this->m_hideScrollbarTimer->stop();
}
delete this->m_hideScrollbarTimer;
this->m_hideScrollbarTimer = nullptr;
}
delete this->m_lastItem;
delete this->m_listItems;
delete this->m_selectedItems;
}
void FileSystemListWidget::clearItems()
{
qDeleteAll(this->m_listItems->begin(), this->m_listItems->end());
this->m_listItems->clear();
}
void FileSystemListWidget::addSelectedItems(QList items, bool recordLastItem)
{
this->m_selectedItems->append(items);
if (recordLastItem && this->m_selectedItems->count() > 0) {
this->m_lastItem = this->m_selectedItems->last();
}
}
void FileSystemListWidget::clearSelectedItems(bool clearLast)
{
this->m_selectedItems->clear();
if (clearLast) {
this->m_lastItem = NULL;
}
}
void FileSystemListWidget::refreshFileSystemItems(QList items)
{
QList *allItems = new QList();
FileSystemListItem *newLastItem = NULL;
for (FileSystemListItem *item:items) {
for (FileSystemListItem *selectionItem:*this->m_selectedItems) {
if (item->isSameItem(selectionItem)) {
allItems->append(item);
break;
}
}
}
if (this->m_lastItem != NULL) {
for (FileSystemListItem *item:items) {
if (item->isSameItem(this->m_lastItem)) {
newLastItem = item;
break;
}
}
}
clearItems();
this->m_listItems->append(items);
clearSelectedItems();
addSelectedItems(*allItems, false);
this->m_lastItem = newLastItem;
this->m_offSet = setOffset(this->m_offSet);
repaint();
}
void FileSystemListWidget::leaveEvent(QEvent * event)
{
hideScrollbar();
QWidget::leaveEvent(event);
}
void FileSystemListWidget::hideScrollbar()
{
this->m_mouseAtScrollArea = false;
this->m_origOffset = this->m_offSet;
repaint();
}
void FileSystemListWidget::mouseMoveEvent(QMouseEvent *mouseEvent)
{
if (this->m_mouseDragScrollbar) {
this->m_offSet = setOffset((mouseEvent->y() - getScrollbarHeight() / 2 - this->m_titleHeight) / (getTheScrollAreaHeight() * 1.0) * this->getItemsTotalHeight());
repaint();
}
else if (mouseAtScrollArea(mouseEvent->x()) != this->m_mouseAtScrollArea) {
this->m_mouseAtScrollArea = mouseAtScrollArea(mouseEvent->x());
repaint();
}
}
void FileSystemListWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
bool isTitleArea = mouseAtTitleArea(event->y());
bool isScrollArea = mouseAtScrollArea(event->x());
if (!isTitleArea && !isScrollArea) {
int pressedItemIndex = (this->m_offSet + event->y() - this->m_titleHeight) / this->m_rowHeight;
if (pressedItemIndex >= this->m_listItems->count()) {
clearSelectedItems();
repaint();
}
else {
if (event->button() == Qt::LeftButton) {
FileSystemListItem *pressItem = (*this->m_listItems)[pressedItemIndex];
bool pressInSelectionArea = false;
for (FileSystemListItem *item : *this->m_selectedItems) {
if (item == pressItem) {
pressInSelectionArea = true;
break;
}
}
if (pressInSelectionArea) {
//open the mount dir
FileSystemListItem *item = static_cast(pressItem);
QString targetPath = QString("file://%1").arg(item->getDirectory());
QDesktopServices::openUrl(QUrl(targetPath));//xdg-open
}
}
}
}
QWidget::mouseDoubleClickEvent(event);
}
void FileSystemListWidget::mousePressEvent(QMouseEvent *mouseEvent)
{
setFocus();
bool isTitleArea = mouseAtTitleArea(mouseEvent->y());
bool isScrollArea = mouseAtScrollArea(mouseEvent->x());
if (isTitleArea) {//点击列表的标题栏区域
if (mouseEvent->button() == Qt::RightButton) {
if (m_columnVisibles.count() == this->columnTitles.count()) {
QMenu *menu = new QMenu();
menu->setObjectName("MonitorMenu");
for (int i = 0; i < m_columnVisibles.count(); i++) {
if (i != 0) {//让第一行总是显示,不可以设置显示或者不显示,其他行可以设置
QAction *action = new QAction(menu);
action->setText(this->columnTitles[i]);
action->setCheckable(true);
action->setChecked(m_columnVisibles[i]);
connect(action, &QAction::triggered, this, [this, action, i] {
m_columnVisibles[i] = !m_columnVisibles[i];
emit this->changeColumnVisible(i, m_columnVisibles[i], m_columnVisibles);
repaint();
});
menu->addAction(action);
}
}
menu->exec(this->mapToGlobal(mouseEvent->pos()));
delete menu;
}
}
}
else if (isScrollArea) {//点击滚动条区域
int barHeight = getScrollbarHeight();
int barY = getScrollbarY();
if (mouseEvent->y() > barY && mouseEvent->y() < barY + barHeight) {
this->m_mouseDragScrollbar = true;
}
else {
this->m_offSet = setOffset((mouseEvent->y() - barHeight / 2 - this->m_titleHeight) / (getTheScrollAreaHeight() * 1.0) * this->getItemsTotalHeight());
repaint();
}
}
else {
int pressedItemIndex = (this->m_offSet + mouseEvent->y() - this->m_titleHeight) / this->m_rowHeight;
if (pressedItemIndex >= this->m_listItems->count()) {
clearSelectedItems();
repaint();
}
else {
if (mouseEvent->button() == Qt::LeftButton) {
if (pressedItemIndex < this->m_listItems->count()) {
if (mouseEvent->modifiers() == Qt::ControlModifier) {
FileSystemListItem *item = (*this->m_listItems)[pressedItemIndex];
if (this->m_selectedItems->contains(item)) {
this->m_selectedItems->removeOne(item);
} else {
QList items = QList();
items << item;
addSelectedItems(items);
}
}
else {
clearSelectedItems();
QList items = QList();
items << (*this->m_listItems)[pressedItemIndex];
addSelectedItems(items);
}
repaint();
}
}
else if (mouseEvent->button() == Qt::RightButton) {
FileSystemListItem *pressItem = (*this->m_listItems)[pressedItemIndex];
bool pressInSelectionArea = false;
for (FileSystemListItem *item : *this->m_selectedItems) {
if (item == pressItem) {
pressInSelectionArea = true;
break;
}
}
if (pressInSelectionArea) {
emit this->rightMouseClickedItem(this->mapToGlobal(mouseEvent->pos()));
}
}
}
}
}
void FileSystemListWidget::mouseReleaseEvent(QMouseEvent *)
{
if (this->m_mouseDragScrollbar) {
this->m_mouseDragScrollbar = false;
repaint();
}
else {
if (this->m_titlePressColumn != -1) {
this->m_titlePressColumn = -1;
repaint();
}
}
}
void FileSystemListWidget::wheelEvent(QWheelEvent *event)
{
if (event->orientation() == Qt::Vertical) {
this->m_origOffset = this->m_offSet;
qreal scrollStep = event->angleDelta().y() / 100.0;
this->m_offSet = setOffset(this->m_offSet - scrollStep * this->m_rowHeight);
repaint();
}
event->accept();
}
void FileSystemListWidget::paintEvent(QPaintEvent *)
{
QPainter painter(this);
painter.setRenderHint(QPainter::Antialiasing, true);
QList titleItemsWidths = getTitleItemsWidths();
painter.setOpacity(0.05);
int penWidth = 1;
QPainterPath framePath;
framePath.addRoundedRect(QRect(rect().x() + penWidth, rect().y() + penWidth, rect().width() - penWidth * 2, rect().height() - penWidth * 2), 5, 5);//背景弧度
painter.setClipPath(framePath);
//标题的背景
if (this->m_titleHeight > 0) {
QPainterPath titlePath;
titlePath.addRect(QRectF(rect().x(), rect().y(), rect().width(), this->m_titleHeight));
painter.setOpacity(0.02);
painter.fillPath(titlePath, QColor("#ffffff"));
}
int title_Y = 0;
int title_Height = 0;
if (this->m_titleHeight > 0) {
int counter = 0;
int posX = 0;
for (int itemWidth:titleItemsWidths) {
if (itemWidth > 0) {
//标题文字
painter.setOpacity(1);
QFont font = painter.font();
font.setPixelSize(12);
painter.setFont(font);
painter.setPen(QPen(QColor("#999999")));
if (this->columnTitles[counter] == tr("Device") || this->columnTitles[counter] == tr("Directory") || this->columnTitles[counter] == tr("Used"))
painter.drawText(QRect(posX + this->m_titlePadding, 0, itemWidth, this->m_titleHeight), Qt::AlignBottom | Qt::AlignLeft, this->columnTitles[counter]);
else
painter.drawText(QRect(posX, 0, itemWidth - this->m_titlePadding, this->m_titleHeight), Qt::AlignBottom | Qt::AlignRight, this->columnTitles[counter]);
//水平下划线
painter.setOpacity(0.8);
QPainterPath h_separatorPath;
h_separatorPath.addRect(QRectF(posX, rect().y() + this->m_titleHeight - 1, itemWidth, 1));
painter.fillPath(h_separatorPath, QColor("#e0e0e0"));
if (counter < titleItemsWidths.size()) {//垂直分割线
QPainterPath v_separatorPath;
v_separatorPath.addRect(QRectF(rect().x() + posX - 1, rect().y() + 5, 1, this->m_titleHeight - 5));
painter.fillPath(v_separatorPath, QColor("#e0e0e0"));
}
posX += itemWidth;
}
counter++;
}
title_Y += this->m_titleHeight;
title_Height += this->m_titleHeight;
}
//去掉列表标题栏后的列表显示区域的背景
painter.setOpacity(0.05);
QPainterPath backgroundPath;
backgroundPath.addRect(QRectF(rect().x(), rect().y() + this->m_titleHeight, rect().width(), rect().height() - this->m_titleHeight));
painter.fillPath(backgroundPath, QColor("#ffffff"));
//挂载的磁盘文件系统信息
QPainterPath scrollAreaPath;
scrollAreaPath.addRect(QRectF(rect().x(), rect().y() + this->m_titleHeight, rect().width(), getTheScrollAreaHeight()));
int rowCounter = 0;
for (FileSystemListItem *item:*this->m_listItems) {
if (rowCounter > ((this->m_offSet - this->m_rowHeight) / this->m_rowHeight)) {
QPainterPath itemPath;
itemPath.addRect(QRect(0, title_Y + rowCounter * this->m_rowHeight - this->m_offSet, rect().width(), this->m_rowHeight));
painter.setClipPath((framePath.intersected(scrollAreaPath)).intersected(itemPath));
bool isSelect = this->m_selectedItems->contains(item);
painter.save();
item->drawBackground(QRect(0, title_Y + rowCounter * this->m_rowHeight - this->m_offSet, rect().width(), this->m_rowHeight), &painter, rowCounter, isSelect);
painter.restore();
int columnCounter = 0;
int columnTitleX = 0;
for (int titleItemWidth : titleItemsWidths) {
if (titleItemWidth > 0) {
painter.save();
if (columnCounter < titleItemsWidths.size() - 1)
item->drawForeground(QRect(columnTitleX, title_Y + rowCounter * this->m_rowHeight - this->m_offSet, titleItemWidth, this->m_rowHeight), &painter, columnCounter, rowCounter, isSelect, true);
else
item->drawForeground(QRect(columnTitleX, title_Y + rowCounter * this->m_rowHeight - this->m_offSet, titleItemWidth, this->m_rowHeight), &painter, columnCounter, rowCounter, isSelect, false);
painter.restore();
columnTitleX += titleItemWidth;
}
columnCounter++;
}
title_Height += this->m_rowHeight;
if (title_Height > rect().height()) {
break;
}
}
rowCounter++;
}
painter.setClipPath(framePath);
//没有挂载的磁盘文件系统信息时绘制提示文字
if (this->m_listItems->size() == 0) {
painter.setOpacity(1);
painter.setPen(QPen(QColor("#666666")));
QFont font = painter.font() ;
font.setPointSize(22);
painter.setFont(font);
painter.drawText(QRect(rect().x(), rect().y() + this->m_titleHeight, rect().width(), rect().height() - this->m_titleHeight), Qt::AlignCenter, tr("No File System Info"));
}
//背景
// QPen framePen;
// framePen.setColor(QColor("#F5F5F5"));
// painter.setPen(framePen);
painter.setOpacity(0.2);
painter.drawPath(framePath);
//垂直滚动条
if (this->m_mouseAtScrollArea) {
paintScrollbar(&painter);
} else if (this->m_origOffset != this->m_offSet) {
paintScrollbar(&painter);
readyToHideScrollbar();
}
}
void FileSystemListWidget::paintScrollbar(QPainter *painter)
{
if (this->getItemsTotalHeight() > getTheScrollAreaHeight()) {
qreal opacitry = 0;
if (this->m_mouseDragScrollbar) {
opacitry = 0.8;
}
else {
if (this->m_mouseAtScrollArea)
opacitry = 0.7;
else
opacitry = 0.5;
}
int barWidth = (this->m_mouseAtScrollArea || this->m_mouseDragScrollbar) ? this->m_scrollbarWidth : 6;
int barY = getScrollbarY();
int barHeight = getScrollbarHeight();
painter->setOpacity(opacitry);
QPainterPath path;
path.addRoundedRect(
QRectF(rect().x() + rect().width() - barWidth - 4, barY + 2, barWidth, barHeight - 2 * 2), 2, 2);//2 is radius
painter->fillPath(path, QColor("#0B95D7"));
QPen pen;
pen.setColor(QColor("#0B95D7"));
pen.setWidth(1);
painter->setOpacity(0);
painter->setPen(pen);
painter->drawPath(path);
}
}
QList FileSystemListWidget::getTitleItemsWidths()
{
QList titleItemsWidths;
if (this->m_columnWidths.length() > 0) {
if (this->m_columnWidths.contains(-1)) {
for (int i = 0; i < this->m_columnWidths.count(); i++) {
if (this->m_columnWidths[i] != -1) {
if (m_columnVisibles[i]) {
titleItemsWidths << this->m_columnWidths[i];
} else {
titleItemsWidths << 0;
}
} else {
if (m_columnVisibles[i]) {
int totalWidth = 0;
for (int j = 0; j < this->m_columnWidths.count(); j++) {
if (this->m_columnWidths[j] != -1 && m_columnVisibles[j]) {
totalWidth += this->m_columnWidths[j];
}
}
titleItemsWidths << rect().width() - totalWidth;
}
else {
titleItemsWidths << 0;
}
}
}
} else {
for (int i = 0; i < this->m_columnWidths.count(); i++) {
if (m_columnVisibles[i]) {
titleItemsWidths << this->m_columnWidths[i];
}
else {
titleItemsWidths << 0;
}
}
}
}
else {
titleItemsWidths << rect().width();
}
return titleItemsWidths;
}
bool FileSystemListWidget::mouseAtScrollArea(int x)
{
return (x > rect().x() + rect().width() - this->m_scrollbarWidth) && (x < rect().x() + rect().width());
}
bool FileSystemListWidget::mouseAtTitleArea(int y)
{
return (y > rect().y() && y < rect().y() + this->m_titleHeight);
}
int FileSystemListWidget::setOffset(int offset)
{
return std::max(0, std::min(offset, getBottomOffset()));
}
int FileSystemListWidget::getItemsTotalHeight()
{
return m_listItems->count() * m_rowHeight;
}
int FileSystemListWidget::getTheScrollAreaHeight()
{
return this->rect().height() - this->m_titleHeight;
}
int FileSystemListWidget::getScrollbarY()
{
return static_cast((this->m_offSet / (this->getItemsTotalHeight() * 1.0)) * getTheScrollAreaHeight() + this->m_titleHeight);
}
int FileSystemListWidget::getScrollbarHeight()
{
return std::max(static_cast(getTheScrollAreaHeight() / (this->getItemsTotalHeight() * 1.0) * rect().height()), 30);//30 is min height
}
int FileSystemListWidget::getBottomOffset()
{
int itemsHeight = this->getItemsTotalHeight();
if (itemsHeight > rect().height() - this->m_titleHeight) {
return this->getItemsTotalHeight() - rect().height() + this->m_titleHeight;
} else {
return 0;
}
}
void FileSystemListWidget::readyToHideScrollbar()
{
if (this->m_hideScrollbarTimer) {
if (this->m_hideScrollbarTimer->isActive())
this->m_hideScrollbarTimer->stop();
}
else {
this->m_hideScrollbarTimer = new QTimer();
this->m_hideScrollbarTimer->setSingleShot(true);
connect(this->m_hideScrollbarTimer, SIGNAL(timeout()), this, SLOT(hideScrollbar()));
}
this->m_hideScrollbarTimer->start(2000);
}