Manitou-Mail logo title

Source file: src/edit_address_widget.cpp

/* Copyright (C) 2004-2009 Daniel Vérité

   This file is part of Manitou-Mail (see http://www.manitou-mail.org)

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License version 2 as
   published by the Free Software Foundation.

   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, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

/* This file contains implementations of edit_address_widget and email_completer classes */

#include "edit_address_widget.h"
#include "addresses.h"
#include "main.h"

#include <QTimer>
#include <QListWidget>
#include <QApplication>
#include <QKeyEvent>
#include <QScrollBar>
#include <QDesktopWidget>

edit_address_widget::edit_address_widget(QWidget* parent) : QLineEdit(parent)
{
  m_timer=NULL;
  popup = new QListWidget();
  popup->setWindowFlags(Qt::Popup);
  popup->setFocusPolicy(Qt::NoFocus);
  popup->setFocusProxy(this);
  popup->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

  completer = new email_completer(this);
  completer->popup=popup;
  completer->lineedit=this;

  this->installEventFilter(completer);
  popup->installEventFilter(completer);

  connect(popup, SIGNAL(itemClicked(QListWidgetItem*)),
	  completer, SLOT(completion_chosen(QListWidgetItem*)));

  connect(popup, SIGNAL(clicked(QModelIndex)), popup, SLOT(hide()));

  connect(this, SIGNAL(textEdited(const QString&)),
	  this, SLOT(check_completions(const QString&)));
}

edit_address_widget::~edit_address_widget()
{
}

void
edit_address_widget::show_popup()
{
  QWidget* widget=this;
  QRect rect;

  const QRect screen = QApplication::desktop()->availableGeometry(widget);
  Qt::LayoutDirection dir = widget->layoutDirection();
  QPoint pos;
  int rw, rh, w;
  int h = (popup->sizeHintForRow(0) * qMin(7, popup->model()->rowCount()) + 3) + 3;
  QScrollBar *hsb = popup->horizontalScrollBar();
  if (hsb && hsb->isVisible())
    h += popup->horizontalScrollBar()->sizeHint().height();

  if (rect.isValid()) {
    rh = rect.height();
    w = rw = rect.width();
    pos = widget->mapToGlobal(dir == Qt::RightToLeft ? rect.bottomRight() : rect.bottomLeft());
  }
  else {
    rh = widget->height();
    rw = widget->width();
    pos = widget->mapToGlobal(QPoint(0, widget->height() - 2));
    w = widget->width();
  }

  if ((pos.x() + rw) > (screen.x() + screen.width()))
    pos.setX(screen.x() + screen.width() - w);
  if (pos.x() < screen.x())
    pos.setX(screen.x());
  if (((pos.y() + rh) > (screen.y() + screen.height())) && ((pos.y() - h - rh) >= 0))
    pos.setY(pos.y() - qMax(h, popup->minimumHeight()) - rh + 2);

  popup->setGeometry(pos.x(), pos.y(), w, h);

  if (!popup->isVisible()) {
    popup->show();
  }
}

void
edit_address_widget::show_completions()
{
  QString prefix; // substring on which the completion is to be based

  int pos = cursorPosition();
  int start = completer->get_prefix_pos(text(), pos);
  if (start>=0) {
    prefix=text().mid(start, pos-start);
  }

  if (prefix.isEmpty()) {
    // nothing to complete
    if (popup->isVisible()) {
      popup->clear();
      popup->hide();
    }
  }
  else {
    mail_address_list completions;
    if (completions.fetch_completions(prefix)) {
      if (!completions.empty()) {
	popup->clear();
	mail_address_list::const_iterator iter;
	for (iter = completions.begin(); iter != completions.end(); iter++) {
	  popup->addItem(QString("%1 <%2>").arg(iter->name()).arg(iter->email()));
	}
	show_popup();
      }
      else {
	popup->clear();
	popup->hide();
      }
    }
  }
}

void
edit_address_widget::check_completions(const QString& newtext)
{
  // Filter out control characters. A common character that can appear
  // at this point is linefeed, and we don't want these in mail addresses
  bool modified = false;
  QString copy = newtext;
  int j=0;
  for (int i=0; i<newtext.length(); i++) {
    if (!newtext.at(i).isPrint()) {
      copy.remove(j, 1);
      modified = true;
    }
    else
      j++;
  }
  if (modified) {
    setText(copy);		// will not recurse
  }
  // Cancel the timer if it's already running.
  if (m_timer)
    delete m_timer;
  m_timer = new QTimer(this);
  m_timer->setSingleShot(true);
  m_timer->setInterval(500);
  connect(m_timer, SIGNAL(timeout()), this, SLOT(show_completions()));
  m_timer->start();
}

email_completer::email_completer(QObject* parent) : QObject(parent)
{
  eatFocusOut=true;
}

email_completer::~email_completer()
{
}

void
email_completer::completion_chosen(QListWidgetItem* item)
{
  if (!item) return;
  QString text= lineedit->text();
  int pos = get_prefix_pos(text, lineedit->cursorPosition());
  QString before = text.mid(0, pos);
  QString after = text.mid(lineedit->cursorPosition());
  lineedit->setText(before + item->text() + after);
}

int
email_completer::get_prefix_pos(const QString text, int cursor_pos)
{
  if (cursor_pos==0)
    return -1;
  int pos = cursor_pos-1;
  while (pos>=0 && text.at(pos).isLetterOrNumber()) {
    pos--;
  }
  if (pos<0 || text.at(pos)==' ' || text.at(pos)==',')
    return pos+1;
  else
    return -1;
}

bool email_completer::eventFilter(QObject* o, QEvent* e)
{
  if (eatFocusOut && o==lineedit && e->type()==QEvent::FocusOut && popup->isVisible()) {
    return true;
  }
  if (o != popup)
    return QObject::eventFilter(o, e);

  switch (e->type()) {
  case QEvent::KeyPress:
    {
      int row=popup->currentRow();
      QKeyEvent *ke = static_cast<QKeyEvent *>(e);
      const int key = ke->key();
      switch (key) {
      case Qt::Key_End:
      case Qt::Key_Home:
	if (ke->modifiers() & Qt::ControlModifier)
	  return false;
	break;

      case Qt::Key_Up:
	if (row>0) {
	  popup->setCurrentRow(row-1);
	  return true;
	}
	else if (popup->count()>0) {
	  popup->setCurrentRow(popup->count()-1);
	  return true;
	}
	else
	  return false;

      case Qt::Key_Down:
	if (row==popup->count()-1) {
	  popup->setCurrentRow(0);
	  return true;
	}
	else if (row < popup->count()-1) {
	  popup->setCurrentRow(row+1);
	  return true;
	}
	else
	  return false;

      case Qt::Key_PageUp:
      case Qt::Key_PageDown:
	return false;
      }

      eatFocusOut = false;
      (static_cast<QObject *>(lineedit))->event(ke);
      eatFocusOut = true;
      if (e->isAccepted() || !popup->isVisible()) {
	// widget lost focus, hide the popup
	if (!lineedit->hasFocus())
	  popup->hide();
	if (e->isAccepted())
	  return true;
      }

        // default implementation for keys not handled by the widget when popup is open
        switch (key) {
        case Qt::Key_Return:
        case Qt::Key_Enter:
        case Qt::Key_Tab:
            popup->hide();
            if (popup->currentItem()) {
	      completion_chosen(popup->currentItem());
	    }
            break;

        case Qt::Key_F4:
            if (ke->modifiers() & Qt::AltModifier)
                popup->hide();
            break;

        case Qt::Key_Backtab:
        case Qt::Key_Escape:
            popup->hide();
            break;

        default:
            break;
        }
	return true;

    } // QEvent::KeyPress
    break;

  case QEvent::MouseButtonPress:
    if (!popup->underMouse()) {
      popup->hide();
      return true;
    }

  case QEvent::InputMethod:
  case QEvent::ShortcutOverride:
    QApplication::sendEvent(lineedit, e);
    break;

  default:
    return false;
  }
  return false;
}

HTML source code generated by GNU Source-Highlight plus some custom post-processing

List of all available source files