9 . Arrastar e Soltar
 Habilitando Arrastar e Soltar
 Suporte a tipos Customizados de Arrastamento
 Controlando a Área ...
private:
bool readFile(const QString &fileName);
QTextEdit *textEdit;
};
A classe MainWindow reimplementa dragEnterEvemt()...
O dropEvent() é chamado quando o usuário solta um objeto no widget. Chamamos
QMimeData::urls() para obter uma lista de QUr...
ProjectListWidget::ProjectListWidget(QWidget *parent)
: QListWidget(parent)
{
setAcceptDrops(true);
}
No construtor, habil...
arrastamento que foi executada (ou Qt::IgnoreAction caso nada tenha sido executado). Qual ação é
executada depende do que ...
Nos exemplos até agora, confiamos no suporte de QMimeData para tipos MIME comuns. Além disso, chamamos
QMimeData::setText(...
QString MyTableWidget::toCsv(const QString &plainText)
{
Qstring result = plaintext;
result.replace(“”, “”);
result.replac...
convertidas em uma tabela HTML. Mas se o usuário arrasta HTML arbitrário para uma QWidgetTable, não
queremos aceitar.
Para...
QStringList TableMimeData::formats()
{
Return myFormats;
}
A função retrieveData() retorna os dados para um dado tipo MIME...
Ao escrever nossas próprias classes, podemos acessar a Área de Transferência através de
QApplication::clipboard(), que ret...
Upcoming SlideShare
Loading in...5
×

Cap9

99

Published on

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
99
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
5
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Transcript of "Cap9"

  1. 1. 9 . Arrastar e Soltar  Habilitando Arrastar e Soltar  Suporte a tipos Customizados de Arrastamento  Controlando a Área de Transferência “Drag and Drop”, ou Arrastar e soltar é uma forma moderna e intuitiva de transferir informação em uma aplicação ou entre aplicações diferentes. È geralmente fornecida em adição ao suporte a clipboard para mover e copiar dados. Neste capítulo, veremos como adicionar suporte a arrastar e soltar a uma aplicação e como controlar formatos customizados. Então mostraremos como reusar o código do arrastar e soltar para adicionar suporte ao clipboard. Esta reutilização de código é possível porque ambos mecanismos são baseados em QMimeData, uma classe que pode fornecer dados em diversos formatos. Habilitando Arrastar e Soltar Drag e Drop envolve duas ações distintas: Arrastar e soltar algo. Widgets do Qt podem servir como pontos de arrastamento, ou como pontos de soltura, ou como ambos. Nosso primeiro exemplo mostra como fazer uma aplicação Qt aceitar um arrastamento iniciado por outra aplicação. A aplicação Qt é uma janela principal como um QTextEdit como seu widget central. Quando o usuário carrega um arquivo texto da Área de Trabalho ou por um explorador de arquivos e o solta na aplicação, a aplicação carrega o arquivo dentro de QTextEdit. Aqui está a definição da classe MainWindow do exemplo: Class MainWindow : public QMainWindow { Q_OBJECT; public: MainWindow(); protected: void dragEnterEvent(QDragEnterEvent *event); void dropEvent(QDropEvent *event);
  2. 2. private: bool readFile(const QString &fileName); QTextEdit *textEdit; }; A classe MainWindow reimplementa dragEnterEvemt() e dropEvent() de QWidget. Já que o propósito desse exemplo é mostrar a funcionalidade arrastar e soltar, a maioria da funcionalidade que esperaríamos ver em uma classe de janela principal foi omitida. MainWindow::MainWindow() { textEdit = new QTextEdit; setCentralWidget (textEdit); textEdit->setAcceptDrops(false); setAcceptDrops(true); setWindowTitle(tr(“Text Editor”)); } No construtor, criamos um QTextEdit e o setamos como o widget central. Por padrão, QTextEdit aceita arrastes textuais de outras aplicações, e se o usuário solta um arquivo dentro dele, o nome do arquivo será inserido no texto. Já que eventos de drop são propagados do filho para o pai, desabilitando a funcionalidade de soltar no QTextEdit e a habilitando na janela principal obtemos os eventos de drop para a janela inteira em MainWindow. void MainWindow::dragEnterEvent (QDragEnterEvent *event) { if(event->mimeData()->hasFormat(“text/uri-list”)) event->acceptProposedAction(); } O dragEnterEvent()é chamado a qualquer momento em que o usuário arrasta um objeto para dentro do widget. Se chamarmos acceptProposedAction() no evento, indicamos que o usuário pode soltar o objeto carregado no widget. Por padrão, o widget não aceitaria o objeto. Qt automaticamente muda o cursor para indicar ao usuário quando o widget é considerado um ponto de soltura. Aqui queremos que o usuário seja autorizado a carregar arquivos, mas nada mais. Para tal, checamos o tipo MIME do carregamento. O tipo MIME text/uri-list é usado para armazenar uma lista de identificadores recursos uniformes (URIs), os quais podem ser nomes de arquivos, URLs (como caminhos HTTP ou FTP), ou outros identificadores de recursos globais. Tipos padrão MIME são definidos pela Autoridade de Números Atribuídos da Internet (IANA). Eles consistem de um tipo e subtipo separados por uma barra. A área de transferência e o sistema de carregar e soltar usam tipos MIME para identificar diferentes tipos de data. A lista oficial de tipos MIME está disponível em http://www.iana.org/assignments/media-types/. void MainWindow::dropEvent(QDropEvent *event) { QList<QUrl> urls = event->mimeData()->urls(); if(urls.isEmpty()) return; QString filename = urls.first().toLocalFile(); if(filename.isEmpty()) return; if(readFile(fileName)) setWindowTitle(tr(“%1 - %2”).arg(fileName) .arg(tr(“Drag File”))); }
  3. 3. O dropEvent() é chamado quando o usuário solta um objeto no widget. Chamamos QMimeData::urls() para obter uma lista de QUrls. Tipicamente, usuários arrastam apenas um arquivo de cada vez, mas é possível arrastar múltiplos arquivos, arrastando uma seleção. Se há mais de uma URL, ou se a URL não é um nome de arquivo local, retornamos imediatamente. QWidget também fornece dragMoveEvent() e dragLeaveEvent(), mas para a maioria das aplicações eles não precisam ser reimplementados. O segundo exemplo ilustra como iniciar um drag e aceitar um drop. Vamos criar uma subclasse QListWidget que suporta carregar e soltar, e usá-la como um componente na aplicação Project Chooser mostrada na figura 9.1. Figura 9.1. A Aplicação Project Chooser A Aplicação Project Chooser apresenta ao usuário dois widgets lista, populados com nomes. Cada widget lista representa um objeto. O usuário pode arrastar e soltar os nomes nos widgets para mover uma pessoa de um projeto para outro. Todo o código de carregar e soltar está localizado na subclasse QListWidget. Aqui está a definição da classe: Class ProjectListWidget : public QListWidget { Q_OBJECT; public: ProjectListWidget(QWidget *parente = 0); protected: void mousePressEvent(QMouseEvent *event); void mouseMoveEvent (QMouseEvent *event); void dragEnterEvent(QDragEnterEvent *event); void dragMoveEvent(QDragMoveEvent *event); void dropEvent(QDropEvent *event); private: void performDrag(); QPoint startPos; }; A classe ProjectListWidget reimplementa cinco controladores de eventos declarados em QWidget.
  4. 4. ProjectListWidget::ProjectListWidget(QWidget *parent) : QListWidget(parent) { setAcceptDrops(true); } No construtor, habilitamos ação de soltar no widget de lista. Void ProjectListWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) startPos = event->pos(); QListWidget::mousePressEvent(event); } Quando o usuário pressiona o botão esquerdo do mouse, armazenamos a posição do mouse na variável privada startPos. Chamamos a implementação QlistWidget de mousePressEvent() para assegurar que QListWidget tem a oportunidade de processas eventos de clique como usualmente. void ProjectListWidget::mouseMoveEvent(QMouseEvent *event) { if(event->buttons() & Qt::LeftButton) { int distance = (event->pos() - startPos).manhattanLength(); if(distance >= QApplication::startDragDistance()) performDrag(); } QListWidget::mouseMoveEvent(event); } Quando o usuário move o cursor enquanto segura o botão esquerdo do mouse, consideramos o início de um drag. Computamos a distância entre a posição atual do mouse e a posição onde o botão esquerdo foi pressionado-a “Manhattan length” é uma aproximação para cálculo rápido do tamanho de um vetor partindo de sua origem. Se a distância é maior ou igual a distância inicial de arrastamento recomendada pela QApplication (normalmente quatro pixels), podemos chamar a função privada performDrag() para começar a arrastar. Isto evita iniciar um drag graças ao movimento da mão do usuário. void ProjectListWidget::performDrag() { QListWidgetItem *item = currentItem(); if (item) { QMimeData *mimeData = new QMimeData; mimeData->setText(item->text()); QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); drag->setPixmap(QPixmap(“:/images/person.png”)); if (drag->exec(Qt::MoveAction) == Qt::MoveAction) delete item; } } Em performDrag(), criamos um objeto do tipo QDrag e setamos this como seu pai. O objeto QDrag armazena os dados em um objeto QMimeData. Para este exemplo, fornecemos os dados no formato string text/plain usando QMimeData::setText(). QMimeData fornece várias funções para controle da maioria dos tipos de itens (imagens, URLs, cores, etc) e podem controlar tipos MIME arbitrários representados como QByteArrays. A chamada para QDrag::setPixmap() seta o ícone que segue o cursor enquanto o objeto é arrastado. A chamada QDrag::exec() inicia a operação de arrastamento e bloqueia até o momento em que o usuário solta a carga arrastada ou cancela o drag. Ela faz uma combinação de chamadas “ações de arrastamento” como argumento (Qt::CopyAction, Qt::MoveAction, e Qt::LinkAction) e retorna a ação de
  5. 5. arrastamento que foi executada (ou Qt::IgnoreAction caso nada tenha sido executado). Qual ação é executada depende do que o widget fonte permite, o que o alvo suporta, e quais teclas modificadoras são pressionadas quando o item é solto. Depois da chamada a exec(), Qt toma posse do objeto carregado e o deleta quando ele não é mais necessário. void ProjectListWidget::dragEnterEvent (QDragEnterEvent *event) { ProjectListWidget *source = qobject_cast<ProjectListWidget *>(event->source()); if (source && source != this) { event->setDropAction(Qt::MoveAction); event->accept(); } } O widget ProjectListWidget não apenas origina arrastamentos, mas também aceita tais arrastamentos caso eles venham de outro ProjectListWidget na mesma aplicação. QDragEnterEvent::source() retorna um ponteiro para o widget que iniciou o arrastamento se o widget for parte da mesma aplicação; caso contrário, retorna um ponteiro nulo. Podemos usar qobject_cast<T>() para assegurar que o arrastamento vem de um ProjectListWidget. Se tudo estiver correto, dizemos ao Qt que estamos prontos para aceitar a ação como uma ação de movimento. void ProjectListWidget::dragMoveEvent(QDragMoveEvent *event) { ProjectListWidget *source = qobject_cast<ProjectListWidget *>(event->source()); if(source && source != this) { event->setDropAction (Qt::MoveAction); event->accept(); } } O código em dragMoveEvent() é idêntico ao que fizemos em dragEnterEvent(). É necessário porque precisamos sobrepor a implementação de QListWidget (na verdade, de QAbstractItemView ) da função. void ProjectListWidget::dropEvent(QDropEvent *event) { ProjectListWidget *source = qobject_cast<ProjectListWidget *>(event->source()); if(source && source != this){ addItem(event->mimeData()->text()); event->setDropAction(Qt::MoveAction); event->accept(); } } Em dropEvent(), obtemos o texto arrastado usando QMimeData::text() e criamos um item com esse texto. Também precisamos aceitar o evento como um “evento de movimentação” para dizer ao widget fonte que ele pode agora remover a versão original do item arrastado. Drag and Drop é um mecanismo poderoso para transferência de dados entre aplicações. Mas em alguns casos, é possível implementar arrastar e soltar sem usar as facilidades de Qt. Se tudo que queremos fazer é mover dados dentro de um widget em uma aplicação, podemos simplesmente reimplementar mousePressEvent() e mouseReleaseEvent(). Suporte a Tipos Customizados de Arrastamento
  6. 6. Nos exemplos até agora, confiamos no suporte de QMimeData para tipos MIME comuns. Além disso, chamamos QMimeData::setText() para criar um arrastamento de texto, e usamos QMimeData::urls() para recuperar o conteúdo de um arrastamento text/uri-list. Se quisermos arrastar texto simples, texto HTML, imagens, URLs ou cores, podemos usar QMimeData sem formalidades. Mas se quisermos arrastar dados customizados, devemos escolher uma entre as alternativas: 1. Podemos providenciar dados arbitrários como um QByteArray usando QMimeData::setData() e os extrair mais tarde usando QMimeData::data(). 2. Podemos criar uma subclasse de QMimeData e reimplementar formats() e retrieveData() para controlar nossos tipos customizados de dados. 3. Para operações de arrastar e soltar dentro de uma aplicação, podemos criar uma subclasse QMimeData e armazenar os dados usando qualquer estrutura de dados que quisermos. A primeira opção não envolve uso de subclasse, mas possui alguns problemas: Precisamos converter nossa estrutura de dados para um QByteArray mesmo se o arrastamento não é aceito por último, e se quisermos fornecer diversos tipos MIME para interagir de uma forma legal com uma grande porção de aplicações, precisamos armazenar os dados diversas vezes (uma vez para cada tipo MIME). Se os dados forem pesados, isso pode desacelerar a aplicação desnecessariamente. As segunda e terceira opções podem evitar ou minimizar esses problemas. Elas nos dão total controle e podem ser usadas em conjunto. Para mostrar como essas alternativas funcionam, mostraremos como adicionar propriedades de arrastar e soltar em um QTableWidget. O arrastamento vai fornecer os seguintes tipos de suportes MIME: text/plain, text/html, e text/csv. Usando a primeira alternativa, iniciar um arrastamento parece assim: Código: void MyTableWidget::mouseMoveEvent (QMouseEvent *event) { if(event->buttons() & Qt::LeftButton) { int distance = (event->pos() - startPos).manhattanLength(); if (distance >= QApplication::startDragDistance()) performDrag(); } QTableWidget::mouseMoveEvent(event); } void MyTableWidget::performDrag() { QString plaintext = selectionAsPlainText(); if(plaintext.isEmpty()) return; QMimeData *mimeData = new QMimeData; mimeData->setText(plainText); mimeData->setHtml(toHtml(plainText)); mimeData->setData(“text/csv”, toCsv(plainText).toUtf8()); QDrag *drag = new QDrag(this); drag->setMimeData(mimeData); if (drag->exec(Qt::CopyAction|Qt::MoceAction)==Qt: :MoveAction) deleteSelection(); } A função private performDrag() é chamada de mouseMoveEvent() para iniciar o arrastamento de uma seleção retangular. Setamos os tipos MIME text/plain e text/html usando setText() e setHtml(), e setamos o tipo text/csv usando setData(), que toma um tipo MIME arbitrário e um QByteArray. O código para selectionAsString() é mais ou menos o mesmo de Spreadsheet::copy() visto no Capítulo 4.
  7. 7. QString MyTableWidget::toCsv(const QString &plainText) { Qstring result = plaintext; result.replace(“”, “”); result.replace(“”, “””); result.replace(“t”, “”””); result.replace(“n”, “”n””); result.prepend(“””); result.append(“””); return result; } QString MyTableWidget::toHtml (const QString &plainText) { QString result = Qt::escape(plainText); result.replace(“t”, “<td>”); result.replace(“n”,”n<tr><td>”); result.prepend(“<table>n<tr><td>”); result.append(“n</table>”); return result; } As funções toCsv() e toHtml() convertem uma string “tabs e novas linhas” para um CSV (valores separados por vírgula) ou um string HTML. Por exemplo, os dados Red Green Blue Cyan Yellow Magenta são convertidos para “Red”, “Green”, “Blue” “Cyan”, “Yellow”, “Magenta” ou para <table> <tr><td>Red<td>Green<Blue> <tr><td>Cyan<td>Yellow<td>Magenta </table> A conversão é feita da maneira mais simples possível, usando QString::replace(). Para exibir caracteres HTML especiais, podemos usar Qt::escape(). void MyTableWidget::dropEvent(QDropEvent *event) { if(event->mimeData()->hasFormat(“text/csv”)) { QByteArray csvData = event->mimeData()->data(“text/csv”); QString csvText = QString::fromUtf8(csvData); … event->acceptProposedAction(); } else if(event->mimeData()->hasFormat(“text/plain”)) { QString plaintext = event->mimeData()->text(); … event->acceptProposedAction(); } } Embora providenciemos os dados em três formas diferentes, apenas duas delas são aceitas em dropEvent(). Se o usuário arrasta células de uma QTableWidget para um editor HTML, queremos que as células sejam
  8. 8. convertidas em uma tabela HTML. Mas se o usuário arrasta HTML arbitrário para uma QWidgetTable, não queremos aceitar. Para fazer este exemplo funcionar, também precisamos chamar setAcceptDrops(true) e setSelectionMode(ConsiguousSelection) no construtor MyTableWidget. Refaçamos agora o exemplo, mas desta vez instanciar a subclasse de QMimeData para postergar ou evitar as (possivelmente caras) conversões entre QTableWiidgetItems e QByteArray. Aqui está a definição da nossa subclasse: class TableMimeData : public QMimeData { Q_OBJECT public: TableMimeData(const QTableWidget *tableWidget, Const QTableWidgetSelectionRange *range); const QTableWidget *tableWidget() const {return myTableWidget; } QTableWidgetSelectionRange range() const { return myRange; } QStringList formats() const; protected: QVariant retrieveData(const QString &format, QVariant::Type preferredType) const; private: static QString toHtml(const QString &plainText); static QString toCsv(const QString &plainText); QString text(int row, int column) const; QString rangeAsPlainText() const; Const QTableWidget *myTableWidget; QTableWIdgetSelectionRange myRange; QStringList myFormat; }; Ao invés de armazenas os dados reais, armazenamos um QTableWidgetSelectionRange que especifica quais células estão sendo arrastadas e mantemos um ponteiro para QTableWidget. As funções formats() e retrieveData() são reimplementadas de QMimeData. TableMimeData::TableMimeData(const QTableWidget *tableWidget, Const QTableWidgetSelectionRange &range) { myTableWidget = tableWidget; myRange = range; myFormats << ‘text/csv” << “text/html” << “text/plain”; } No construtor, inicializamos as variáveis privadas. QStringList TableMimeData::formats() const { Return myFormats; } A função formats() retorna uma lista de tipos MIME fornecidos pelo objeto de dados MIME. A ordem precisa dos formatos é geralmente irrelevante, mas é uma boa prática colocar os “melhores” formatos primeiro. Aplicações que suportam muitos formatos usarão algumas vezes o primeiro formato que for compatível.
  9. 9. QStringList TableMimeData::formats() { Return myFormats; } A função retrieveData() retorna os dados para um dado tipo MIME na forma de QVariant. O valor do parâmetro de format é normalmente uma das strings retornadas por formats(), mas não podemos assumir que, já que nem todas as aplicações checam o tipo MIME frente a formats(). As funções text(), html(), urls(), imageData(), colorData(), e data() fornecidas por QMimeData são implementadas em termos de retrieveData(). O parâmetro preferredType nos dá uma dica sobre qual tipo devemos inserir na QVariant. Aqui, ignoramos isto e confiamos a QMimeData a tarefa de converter o valor de retorno no tipo desejado, se for necessário. void MyTableWidget::dropEvent(QDropEvent *event) { const TableMimeData *tableData = qobject_cast<const TableMimeData *>(event->mimeData()); if(tableData){ const QTableWidget *otherTable = tableData->tableWidget(); QTableWidgetSelectionRange ptherRange = tableData->range(); … event->acceptProposedAction(); } else if (event->mimeData()->hasFormat(“text/csv”)) { QByteArray csvData = event->mimeData()->data(“text/csv”); QString csvText = QString::fromUtf8(csvData); … event->acceptProposedAction(); } else if (event->mimeData()->hasFormat(“text/plain”)) { QString plaintext = event->mimeData()->text(); … event->acceptProposedAction(); } QTableWidget::mouseMoveEvent(event); } A função dropEvent() é parecida com a que tivemos mais cedo nesta sessão, mas desta vez a otimizamos, verificando se podemos converter com segurança o objeto QMimeData em um TableMimeData. Se qobject_cast<T>() funcionar, significa que o arrastamento foi originado de um MyTableWidget na mesma aplicação, e podemos diretamente acessar os dados da tabela ao invés de ir através da API de QMimeData. Se a conversão falhar, extraímos os dados na maneira padrão. Neste exemplo, codificamos o texto CSV usando a formatação UTF-8. Se quisermos ter certeza de estar usando a formatação correta, poderíamos usar o parâmetro charset do tipo MIME text/plain para especificar uma formatação explícita. Aqui estão alguns exemplos: text/plain;charset=US-ASCII text/plain;charset=ISSO-8859-1 text/plain;charset=Shift_JIS text/plain;charset=UTF-8 Controle a Área de Transferência A maioria das aplicações faz uso do controle de transferência pré-construído do Qt de uma forma ou de outra. Por exemplo, a classe QTextEdit fornece opções de cut(), copy() e paste() bem como atalhos de teclado, assim pouco ou nenhum código adicional é necessário.
  10. 10. Ao escrever nossas próprias classes, podemos acessar a Área de Transferência através de QApplication::clipboard(), que retorna um ponteiro para o objeto QClipboard da aplicação. Controlar a área de transferência do sistema é fácil: faça uma chamada a setText(), setImage(), ou setPixmap() para inserir dados na área de transferência, e chame text(), image() ou pixmap() para recuperar dados da área de transferência. Já vimos exemplos de uso da área de transferência na aplicação Spreadsheet do Capítulo 4. Para algumas aplicações, a funcionalidade construída pode não ser suficiente. Por exemplo, pode ser que queiramos fornecer que não é apenas texto ou imagem, ou podemos vir a querer fornecer dados em diversos formatos para máxima interoperabilidade com outras aplicações. Este caso é muito similar com o que encontramos mais cedo em arrastar e soltar, e a resposta também é similar: Podemos chamar uma subclasse de QMimeData e reimplementar alguns funções virtuais. Caso nossa aplicação suporte drag and drop através de uma subclasse customizada QMimeData, podemos simplesmente reusar a subclasse QMimeData e a colocar na área de transferência usando a função setMimeData(). Para recuperar os dados, podemos chamar mimeData() na área de transferência. Em X11, geralmente é possível colar uma seleção clicando no botão do meio de um mouse de três botões. Isto é feito usando uma área de transferência “de seleção” separada. Se quiser que seus widgets forneçam este tipo de área de transferência assim como a padrão, você deve passar QClipboard::Selection como um argumento adicional para as várias chamadas chamas de área de transferência. Por exemplo, aqui vemos como reimplementaríamos mouseReleaseEvent() em um editor de texto para fornecer suporte a ação de colar usando o botão do meio do mouse: void MyTextEditor::mouseReleaseEvent(QMouseEvent *event) { QClipboard *clipboard = QApplication::clipboard(); if (event->button() == Qt::MidButton && clipboard->supportsSelection() { QString text = clipboard->text(QClipboard::Selection); pasteText(text); } } Em X11, a função supportsSelection() retorna true. Em outras plataformas, retorna false. Se quisermos ser notificados sempre que o conteúdo da área de transferência muda, podemos conectar o sinal QClipboard::dataChanged() a uma opção customizada.

×