Add font format support for cell data
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
|
||||
|
||||
## References
|
||||
|
||||
* https://github.com/jmcnamara/XlsxWriter
|
||||
* http://officeopenxml.com/anatomyofOOXML-xlsx.php
|
||||
* http://www.libxl.com
|
||||
* http://closedxml.codeplex.com/
|
||||
* http://search.cpan.org/~jmcnamara/Excel-Writer-XLSX-0.71/
|
||||
@@ -1,3 +1,6 @@
|
||||
TEMPLATE = subdirs
|
||||
SUBDIRS = hello
|
||||
|
||||
SUBDIRS += \
|
||||
style
|
||||
|
||||
|
||||
@@ -18,6 +18,8 @@ int main()
|
||||
sheet->write("D7", true);
|
||||
|
||||
QXlsx::Worksheet *sheet2 = workbook.addWorksheet();
|
||||
//Rows and columns are zero indexed.
|
||||
//The first cell in a worksheet, "A1", is (0, 0).
|
||||
sheet2->write(0, 0, "First");
|
||||
sheet2->write(1, 0, "Second");
|
||||
sheet2->write(2, 0, "Third");
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
#include <QtCore>
|
||||
#include "xlsxworkbook.h"
|
||||
#include "xlsxworksheet.h"
|
||||
#include "xlsxformat.h"
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
# define DATA_PATH "../../../"
|
||||
#else
|
||||
# define DATA_PATH "./"
|
||||
#endif
|
||||
|
||||
int main()
|
||||
{
|
||||
QXlsx::Workbook workbook;
|
||||
QXlsx::Worksheet *sheet = workbook.addWorksheet();
|
||||
|
||||
QXlsx::Format *format1 = workbook.addFormat();
|
||||
format1->setFontColor(QColor(Qt::red));
|
||||
format1->setFontSize(15);
|
||||
sheet->write("A1", "Hello Qt!", format1);
|
||||
sheet->write("B3", 12345, format1);
|
||||
|
||||
QXlsx::Format *format2 = workbook.addFormat();
|
||||
format2->setFontBold(true);
|
||||
format2->setFontUnderline(QXlsx::Format::FontUnderlineDouble);
|
||||
sheet->write("C5", "=44+33", format2);
|
||||
sheet->write("D7", true, format2);
|
||||
|
||||
workbook.save(DATA_PATH"TestStyle.xlsx");
|
||||
workbook.save(DATA_PATH"TestStyle.zip");
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
TARGET = style
|
||||
|
||||
include(../../src/qtxlsxwriter.pri)
|
||||
|
||||
SOURCES += main.cpp
|
||||
+109
-61
@@ -26,24 +26,32 @@
|
||||
|
||||
namespace QXlsx {
|
||||
|
||||
Format::Format(QObject *parent) :
|
||||
QObject(parent)
|
||||
Format::Format()
|
||||
{
|
||||
m_font.bold = false;
|
||||
m_font.color = QColor(Qt::black);
|
||||
m_font.italic = false;
|
||||
m_font.name = "Calibri";
|
||||
m_font.scirpt = FontScriptNormal;
|
||||
m_font.size = 11;
|
||||
m_font.strikeOut = false;
|
||||
m_font.underline = FontUnderlineNone;
|
||||
m_font.shadow = false;
|
||||
m_font.outline = false;
|
||||
m_font.family = 2;
|
||||
m_font.scheme = "minor";
|
||||
m_font.charset = 0;
|
||||
m_font.condense = 0;
|
||||
m_font.extend = 0;
|
||||
m_font.redundant = false;
|
||||
m_font.index = 0;
|
||||
|
||||
m_is_dxf_fomat = false;
|
||||
|
||||
m_xf_index = 0;
|
||||
m_dxf_index = 0;
|
||||
|
||||
m_num_format_index = 0;
|
||||
|
||||
m_has_font = false;
|
||||
m_font_index = 0;
|
||||
m_font_family = 2;
|
||||
m_font_scheme = "minor";
|
||||
|
||||
m_font.setFamily("Calibri");
|
||||
m_font.setPointSize(11);
|
||||
|
||||
m_theme = 0;
|
||||
m_color_indexed = 0;
|
||||
|
||||
@@ -54,15 +62,101 @@ Format::Format(QObject *parent) :
|
||||
m_border_index = false;
|
||||
}
|
||||
|
||||
int Format::fontSize() const
|
||||
{
|
||||
return m_font.size;
|
||||
}
|
||||
|
||||
void Format::setFontSize(int size)
|
||||
{
|
||||
m_font.size = size;
|
||||
}
|
||||
|
||||
bool Format::fontItalic() const
|
||||
{
|
||||
return m_font.italic;
|
||||
}
|
||||
|
||||
void Format::setFontItalic(bool italic)
|
||||
{
|
||||
m_font.italic = italic;
|
||||
}
|
||||
|
||||
bool Format::fontStrikeOut() const
|
||||
{
|
||||
return m_font.strikeOut;
|
||||
}
|
||||
|
||||
void Format::setFontStricOut(bool stricOut)
|
||||
{
|
||||
m_font.strikeOut = stricOut;
|
||||
}
|
||||
|
||||
QColor Format::fontColor() const
|
||||
{
|
||||
return m_font.color;
|
||||
}
|
||||
|
||||
void Format::setFontColor(const QColor &color)
|
||||
{
|
||||
m_font.color = color;
|
||||
}
|
||||
|
||||
bool Format::fontBold() const
|
||||
{
|
||||
return m_font.bold;
|
||||
}
|
||||
|
||||
void Format::setFontBold(bool bold)
|
||||
{
|
||||
m_font.bold = bold;
|
||||
}
|
||||
|
||||
Format::FontScript Format::fontScript() const
|
||||
{
|
||||
return m_font.scirpt;
|
||||
}
|
||||
|
||||
void Format::setFontScript(FontScript script)
|
||||
{
|
||||
m_font.scirpt = script;
|
||||
}
|
||||
|
||||
Format::FontUnderline Format::fontUnderline() const
|
||||
{
|
||||
return m_font.underline;
|
||||
}
|
||||
|
||||
void Format::setFontUnderline(FontUnderline underline)
|
||||
{
|
||||
m_font.underline = underline;
|
||||
}
|
||||
|
||||
bool Format::fontOutline() const
|
||||
{
|
||||
return m_font.outline;
|
||||
}
|
||||
|
||||
void Format::setFontOutline(bool outline)
|
||||
{
|
||||
m_font.outline = outline;
|
||||
}
|
||||
|
||||
QString Format::fontName() const
|
||||
{
|
||||
return m_font.name;
|
||||
}
|
||||
|
||||
void Format::setFontName(const QString &name)
|
||||
{
|
||||
m_font.name = name;
|
||||
}
|
||||
|
||||
bool Format::isDxfFormat() const
|
||||
{
|
||||
return m_is_dxf_fomat;
|
||||
}
|
||||
|
||||
void Format::setFont(const QFont &font)
|
||||
{
|
||||
m_font = font;
|
||||
}
|
||||
|
||||
void Format::setForegroundColor(const QColor &color)
|
||||
{
|
||||
@@ -74,50 +168,4 @@ void Format::setBackgroundColor(const QColor &color)
|
||||
m_bg_color = color;
|
||||
}
|
||||
|
||||
QString Format::fontName() const
|
||||
{
|
||||
return m_font.family();
|
||||
}
|
||||
|
||||
bool Format::bold() const
|
||||
{
|
||||
return m_font.weight() == QFont::Bold;
|
||||
}
|
||||
|
||||
bool Format::italic() const
|
||||
{
|
||||
return m_font.italic();
|
||||
}
|
||||
|
||||
bool Format::fontOutline() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Format::fontShadow() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Format::fontStrikout() const
|
||||
{
|
||||
return m_font.strikeOut();
|
||||
}
|
||||
|
||||
bool Format::fontUnderline() const
|
||||
{
|
||||
return m_font.underline();
|
||||
}
|
||||
|
||||
QColor Format::fontColor() const
|
||||
{
|
||||
return m_font_color;
|
||||
}
|
||||
|
||||
int Format::fontSize() const
|
||||
{
|
||||
return m_font.pointSize();
|
||||
}
|
||||
|
||||
|
||||
} // namespace QXlsx
|
||||
|
||||
+72
-22
@@ -25,48 +25,100 @@
|
||||
#ifndef QXLSX_FORMAT_H
|
||||
#define QXLSX_FORMAT_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QFont>
|
||||
#include <QColor>
|
||||
|
||||
namespace QXlsx {
|
||||
|
||||
class Styles;
|
||||
class Worksheet;
|
||||
|
||||
class Format : public QObject
|
||||
class Format
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
void setFont(const QFont &font);
|
||||
enum FontScript
|
||||
{
|
||||
FontScriptNormal,
|
||||
FontScriptSuper,
|
||||
FontScriptSub
|
||||
};
|
||||
|
||||
enum FontUnderline
|
||||
{
|
||||
FontUnderlineNone,
|
||||
FontUnderlineSingle,
|
||||
FontUnderlineDouble,
|
||||
FontUnderlineSingleAccounting,
|
||||
FontUnderlineDoubleAccounting
|
||||
};
|
||||
|
||||
int fontSize() const;
|
||||
void setFontSize(int size);
|
||||
bool fontItalic() const;
|
||||
void setFontItalic(bool italic);
|
||||
bool fontStrikeOut() const;
|
||||
void setFontStricOut(bool);
|
||||
QColor fontColor() const;
|
||||
void setFontColor(const QColor &);
|
||||
bool fontBold() const;
|
||||
void setFontBold(bool bold);
|
||||
FontScript fontScript() const;
|
||||
void setFontScript(FontScript);
|
||||
FontUnderline fontUnderline() const;
|
||||
void setFontUnderline(FontUnderline);
|
||||
bool fontOutline() const;
|
||||
void setFontOutline(bool outline);
|
||||
QString fontName() const;
|
||||
void setFontName(const QString &);
|
||||
|
||||
void setForegroundColor(const QColor &color);
|
||||
void setBackgroundColor(const QColor &color);
|
||||
|
||||
private:
|
||||
friend class Styles;
|
||||
explicit Format(QObject *parent = 0);
|
||||
friend class Worksheet;
|
||||
explicit Format();
|
||||
|
||||
struct Font
|
||||
{
|
||||
int size;
|
||||
bool italic;
|
||||
bool strikeOut;
|
||||
QColor color;
|
||||
bool bold;
|
||||
FontScript scirpt;
|
||||
FontUnderline underline;
|
||||
bool outline;
|
||||
bool shadow;
|
||||
QString name;
|
||||
int family;
|
||||
int charset;
|
||||
QString scheme;
|
||||
int condense;
|
||||
int extend;
|
||||
|
||||
//helper member
|
||||
bool redundant; //same with the fonts used by some other Formats
|
||||
int index; //index in the Font list
|
||||
} m_font;
|
||||
|
||||
bool hasFont() const {return !m_font.redundant;}
|
||||
int fontIndex() const {return m_font.index;}
|
||||
void setFontIndex(int index) {m_font.index = index;}
|
||||
int fontFamily() const{return m_font.family;}
|
||||
bool fontShadow() const {return m_font.shadow;}
|
||||
QString fontScheme() const {return m_font.scheme;}
|
||||
|
||||
|
||||
bool isDxfFormat() const;
|
||||
int xfIndex() const {return m_xf_index;}
|
||||
void setXfIndex(int index) {m_xf_index = index; m_font.index=index;}
|
||||
|
||||
//num
|
||||
int numFormatIndex() const {return m_num_format_index;}
|
||||
|
||||
//fonts
|
||||
bool hasFont() const {return m_has_font;}
|
||||
int fontIndex() const {return m_font_index;}
|
||||
QString fontName() const;
|
||||
bool bold() const;
|
||||
bool italic() const;
|
||||
bool fontStrikout() const;
|
||||
bool fontOutline() const;
|
||||
bool fontShadow() const;
|
||||
bool fontUnderline() const;
|
||||
QColor fontColor() const;
|
||||
int fontSize() const;
|
||||
int fontFamily() const{return m_font_family;}
|
||||
int theme() const {return m_theme;}
|
||||
int colorIndexed() const {return m_color_indexed;}
|
||||
QString fontScheme() const {return m_font_scheme;}
|
||||
void setHasFont(bool has) {m_has_font=has;}
|
||||
|
||||
//fills
|
||||
bool hasFill() const {return m_has_fill;}
|
||||
@@ -86,10 +138,8 @@ private:
|
||||
|
||||
bool m_has_font;
|
||||
int m_font_index;
|
||||
QFont m_font;
|
||||
int m_font_family;
|
||||
QString m_font_scheme;
|
||||
QColor m_font_color;
|
||||
QColor m_bg_color;
|
||||
QColor m_fg_color;
|
||||
int m_theme;
|
||||
|
||||
+24
-7
@@ -35,17 +35,19 @@ Styles::Styles(QObject *parent) :
|
||||
{
|
||||
m_fill_count = 2; //Starts from 2
|
||||
m_borders_count = 1;
|
||||
m_font_count = 1;
|
||||
m_font_count = 0;
|
||||
|
||||
//Add the default cell format
|
||||
Format *format = addFormat();
|
||||
format->setHasFont(true);
|
||||
format->setHasBorder(true);
|
||||
}
|
||||
|
||||
Format *Styles::addFormat()
|
||||
{
|
||||
Format *format = new Format(this);
|
||||
Format *format = new Format();
|
||||
format->setXfIndex(m_formats.size());
|
||||
m_font_count += 1;
|
||||
|
||||
m_formats.append(format);
|
||||
return format;
|
||||
}
|
||||
@@ -109,18 +111,33 @@ void Styles::writeFonts(XmlStreamWriter &writer)
|
||||
foreach (Format *format, m_xf_formats) {
|
||||
if (format->hasFont()) {
|
||||
writer.writeStartElement("font");
|
||||
if (format->bold())
|
||||
if (format->fontBold())
|
||||
writer.writeEmptyElement("b");
|
||||
if (format->italic())
|
||||
if (format->fontItalic())
|
||||
writer.writeEmptyElement("i");
|
||||
if (format->fontStrikout())
|
||||
if (format->fontStrikeOut())
|
||||
writer.writeEmptyElement("strike");
|
||||
if (format->fontOutline())
|
||||
writer.writeEmptyElement("outline");
|
||||
if (format->fontShadow())
|
||||
writer.writeEmptyElement("shadow");
|
||||
if (format->fontUnderline()) //More option
|
||||
if (format->fontUnderline() != Format::FontUnderlineNone) {
|
||||
writer.writeEmptyElement("u");
|
||||
if (format->fontUnderline() == Format::FontUnderlineDouble)
|
||||
writer.writeAttribute("val", "double");
|
||||
else if (format->fontUnderline() == Format::FontUnderlineSingleAccounting)
|
||||
writer.writeAttribute("val", "singleAccounting");
|
||||
else if (format->fontUnderline() == Format::FontUnderlineDoubleAccounting)
|
||||
writer.writeAttribute("val", "doubleAccounting");
|
||||
}
|
||||
if (format->fontScript() != Format::FontScriptNormal) {
|
||||
writer.writeEmptyElement("vertAligh");
|
||||
if (format->fontScript() == Format::FontScriptSuper)
|
||||
writer.writeAttribute("val", "superscript");
|
||||
else
|
||||
writer.writeAttribute("val", "subscript");
|
||||
}
|
||||
|
||||
if (!format->isDxfFormat()) {
|
||||
writer.writeEmptyElement("sz");
|
||||
writer.writeAttribute("val", QString::number(format->fontSize()));
|
||||
|
||||
+24
-21
@@ -24,6 +24,7 @@
|
||||
****************************************************************************/
|
||||
#include "xlsxworksheet.h"
|
||||
#include "xlsxworkbook.h"
|
||||
#include "xlsxformat.h"
|
||||
#include "xlsxutility_p.h"
|
||||
#include "xlsxsharedstrings_p.h"
|
||||
#include "xmlstreamwriter_p.h"
|
||||
@@ -124,34 +125,34 @@ void Worksheet::setZeroValuesHidden(bool enable)
|
||||
m_show_zeros = !enable;
|
||||
}
|
||||
|
||||
int Worksheet::write(int row, int column, const QVariant &value)
|
||||
int Worksheet::write(int row, int column, const QVariant &value, Format *format)
|
||||
{
|
||||
bool ok;
|
||||
int ret = 0;
|
||||
|
||||
if (value.isNull()) { //blank
|
||||
ret = writeBlank(row, column);
|
||||
ret = writeBlank(row, column, format);
|
||||
} else if (value.type() == QMetaType::Bool) { //Bool
|
||||
ret = writeBool(row,column, value.toBool());
|
||||
ret = writeBool(row,column, value.toBool(), format);
|
||||
} else if (value.toDateTime().isValid()) { //DateTime
|
||||
|
||||
} else if (value.toDouble(&ok), ok) { //Number
|
||||
if (!m_workbook->isStringsToNumbersEnabled() && value.type() == QMetaType::QString) {
|
||||
//Don't convert string to number if the flag not enabled.
|
||||
ret = writeString(row, column, value.toString());
|
||||
ret = writeString(row, column, value.toString(), format);
|
||||
} else {
|
||||
ret = writeNumber(row, column, value.toDouble());
|
||||
ret = writeNumber(row, column, value.toDouble(), format);
|
||||
}
|
||||
} else if (value.type() == QMetaType::QUrl) { //url
|
||||
|
||||
} else if (value.type() == QMetaType::QString) { //string
|
||||
QString token = value.toString();
|
||||
if (token.startsWith("=")) {
|
||||
ret = writeFormula(row, column, token);
|
||||
ret = writeFormula(row, column, token, format);
|
||||
} else if (token.startsWith("{") && token.endsWith("}")) {
|
||||
|
||||
} else {
|
||||
ret = writeString(row, column, token);
|
||||
ret = writeString(row, column, token, format);
|
||||
}
|
||||
} else { //Wrong type
|
||||
|
||||
@@ -162,16 +163,16 @@ int Worksheet::write(int row, int column, const QVariant &value)
|
||||
}
|
||||
|
||||
//convert the "A1" notation to row/column notation
|
||||
int Worksheet::write(const QString row_column, const QVariant &value)
|
||||
int Worksheet::write(const QString row_column, const QVariant &value, Format *format)
|
||||
{
|
||||
QPoint pos = xl_cell_to_rowcol(row_column);
|
||||
if (pos == QPoint(-1, -1)) {
|
||||
return -1;
|
||||
}
|
||||
return write(pos.x(), pos.y(), value);
|
||||
return write(pos.x(), pos.y(), value, format);
|
||||
}
|
||||
|
||||
int Worksheet::writeString(int row, int column, const QString &value)
|
||||
int Worksheet::writeString(int row, int column, const QString &value, Format *format)
|
||||
{
|
||||
int error = 0;
|
||||
QString content = value;
|
||||
@@ -186,20 +187,20 @@ int Worksheet::writeString(int row, int column, const QString &value)
|
||||
SharedStrings *sharedStrings = m_workbook->sharedStrings();
|
||||
int index = sharedStrings->addSharedString(content);
|
||||
|
||||
m_table[row][column] = XlsxCellData(index, XlsxCellData::String);
|
||||
m_table[row][column] = XlsxCellData(index, XlsxCellData::String, format);
|
||||
return error;
|
||||
}
|
||||
|
||||
int Worksheet::writeNumber(int row, int column, double value)
|
||||
int Worksheet::writeNumber(int row, int column, double value, Format *format)
|
||||
{
|
||||
if (checkDimensions(row, column))
|
||||
return -1;
|
||||
|
||||
m_table[row][column] = XlsxCellData(value, XlsxCellData::Number);
|
||||
m_table[row][column] = XlsxCellData(value, XlsxCellData::Number, format);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Worksheet::writeFormula(int row, int column, const QString &content, double result)
|
||||
int Worksheet::writeFormula(int row, int column, const QString &content, Format *format, double result)
|
||||
{
|
||||
int error = 0;
|
||||
QString formula = content;
|
||||
@@ -210,28 +211,28 @@ int Worksheet::writeFormula(int row, int column, const QString &content, double
|
||||
if (formula.startsWith("="))
|
||||
formula.remove(0,1);
|
||||
|
||||
XlsxCellData data(result, XlsxCellData::Formula);
|
||||
XlsxCellData data(result, XlsxCellData::Formula, format);
|
||||
data.formula = formula;
|
||||
m_table[row][column] = data;
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
int Worksheet::writeBlank(int row, int column)
|
||||
int Worksheet::writeBlank(int row, int column, Format *format)
|
||||
{
|
||||
if (checkDimensions(row, column))
|
||||
return -1;
|
||||
|
||||
m_table[row][column] = XlsxCellData(QVariant(), XlsxCellData::Blank);
|
||||
m_table[row][column] = XlsxCellData(QVariant(), XlsxCellData::Blank, format);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Worksheet::writeBool(int row, int column, bool value)
|
||||
int Worksheet::writeBool(int row, int column, bool value, Format *format)
|
||||
{
|
||||
if (checkDimensions(row, column))
|
||||
return -1;
|
||||
|
||||
m_table[row][column] = XlsxCellData(value, XlsxCellData::Boolean);
|
||||
m_table[row][column] = XlsxCellData(value, XlsxCellData::Boolean, format);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -386,8 +387,10 @@ void Worksheet::writeCellData(XmlStreamWriter &writer, int row, int col, const X
|
||||
|
||||
writer.writeStartElement("c");
|
||||
writer.writeAttribute("r", cell_range);
|
||||
//Style used by the cell, row /col
|
||||
// writer.writeAttribute("s", QString::number(""));
|
||||
|
||||
//Style used by the cell, row or col
|
||||
if (data.format)
|
||||
writer.writeAttribute("s", QString::number(data.format->xfIndex()));
|
||||
|
||||
if (data.dataType == XlsxCellData::String) {
|
||||
//data.data: Index of the string in sharedStringTable
|
||||
|
||||
+9
-9
@@ -47,8 +47,8 @@ struct XlsxCellData
|
||||
ArrayFormula,
|
||||
Boolean
|
||||
};
|
||||
XlsxCellData(const QVariant &data=QVariant(), CellDataType type=Blank) :
|
||||
value(data), dataType(type), format(0)
|
||||
XlsxCellData(const QVariant &data=QVariant(), CellDataType type=Blank, Format *format=0) :
|
||||
value(data), dataType(type), format(format)
|
||||
{
|
||||
|
||||
}
|
||||
@@ -63,13 +63,13 @@ class Worksheet : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
int write(const QString row_column, const QVariant &value);
|
||||
int write(int row, int column, const QVariant &value);
|
||||
int writeString(int row, int column, const QString &value);
|
||||
int writeNumber(int row, int column, double value);
|
||||
int writeFormula(int row, int column, const QString &formula, double result=0);
|
||||
int writeBlank(int row, int column);
|
||||
int writeBool(int row, int column, bool value);
|
||||
int write(const QString row_column, const QVariant &value, Format *format=0);
|
||||
int write(int row, int column, const QVariant &value, Format *format=0);
|
||||
int writeString(int row, int column, const QString &value, Format *format=0);
|
||||
int writeNumber(int row, int column, double value, Format *format=0);
|
||||
int writeFormula(int row, int column, const QString &formula, Format *format=0, double result=0);
|
||||
int writeBlank(int row, int column, Format *format=0);
|
||||
int writeBool(int row, int column, bool value, Format *format=0);
|
||||
|
||||
void setRightToLeft(bool enable);
|
||||
void setZeroValuesHidden(bool enable);
|
||||
|
||||
@@ -47,7 +47,6 @@ void ZipWriter::addFile(const QString &filePath, QIODevice *device)
|
||||
|
||||
void ZipWriter::addFile(const QString &filePath, const QByteArray &data)
|
||||
{
|
||||
qDebug()<<filePath<<data.size();
|
||||
m_writer->addFile(filePath, data);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user