diff options
Diffstat (limited to 'Swift/QtUI')
57 files changed, 1398 insertions, 602 deletions
diff --git a/Swift/QtUI/.gitignore b/Swift/QtUI/.gitignore index f539e86..53acb9f 100644 --- a/Swift/QtUI/.gitignore +++ b/Swift/QtUI/.gitignore @@ -1,3 +1,4 @@ Swift BuildVersion.h *.dmg +swift-open-uri diff --git a/Swift/QtUI/ChatList/ChatListDelegate.cpp b/Swift/QtUI/ChatList/ChatListDelegate.cpp index 274a10a..29dba62 100644 --- a/Swift/QtUI/ChatList/ChatListDelegate.cpp +++ b/Swift/QtUI/ChatList/ChatListDelegate.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -11,6 +11,7 @@ #include "Swift/QtUI/Roster/GroupItemDelegate.h" #include "Swift/QtUI/ChatList/ChatListItem.h" #include "Swift/QtUI/ChatList/ChatListMUCItem.h" +#include "Swift/QtUI/ChatList/ChatListRecentItem.h" #include "Swift/QtUI/ChatList/ChatListGroupItem.h" namespace Swift { @@ -27,7 +28,11 @@ QSize ChatListDelegate::sizeHint(const QStyleOptionViewItem& option, const QMode ChatListItem* item = static_cast<ChatListItem*>(index.internalPointer()); if (item && dynamic_cast<ChatListMUCItem*>(item)) { return mucSizeHint(option, index); - } else if (item && dynamic_cast<ChatListGroupItem*>(item)) { + } + else if (item && dynamic_cast<ChatListRecentItem*>(item)) { + return common_.contactSizeHint(option, index); + } + else if (item && dynamic_cast<ChatListGroupItem*>(item)) { return groupDelegate_->sizeHint(option, index); } return QStyledItemDelegate::sizeHint(option, index); @@ -40,14 +45,23 @@ QSize ChatListDelegate::mucSizeHint(const QStyleOptionViewItem& /*option*/, cons return QSize(150, sizeByText); } +QSize ChatListDelegate::recentSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const { + return mucSizeHint(option, index); +} + void ChatListDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { ChatListItem* item = static_cast<ChatListItem*>(index.internalPointer()); if (item && dynamic_cast<ChatListMUCItem*>(item)) { paintMUC(painter, option, dynamic_cast<ChatListMUCItem*>(item)); - } else if (item && dynamic_cast<ChatListGroupItem*>(item)) { + } + else if (item && dynamic_cast<ChatListRecentItem*>(item)) { + paintRecent(painter, option, dynamic_cast<ChatListRecentItem*>(item)); + } + else if (item && dynamic_cast<ChatListGroupItem*>(item)) { ChatListGroupItem* group = dynamic_cast<ChatListGroupItem*>(item); groupDelegate_->paint(painter, option, group->data(Qt::DisplayRole).toString(), group->rowCount(), option.state & QStyle::State_Open); - } else { + } + else { QStyledItemDelegate::paint(painter, option, index); } } @@ -78,9 +92,24 @@ void ChatListDelegate::paintMUC(QPainter* painter, const QStyleOptionViewItem& o painter->setPen(QPen(QColor(160,160,160))); QRect detailRegion(textRegion.adjusted(0, nameHeight, 0, 0)); - DelegateCommons::drawElidedText(painter, detailRegion, item->data(DetailTextRole).toString()); + DelegateCommons::drawElidedText(painter, detailRegion, item->data(ChatListMUCItem::DetailTextRole).toString()); painter->restore(); } +void ChatListDelegate::paintRecent(QPainter* painter, const QStyleOptionViewItem& option, ChatListRecentItem* item) const { + QColor nameColor = item->data(Qt::TextColorRole).value<QColor>(); + QString avatarPath; + if (item->data(ChatListRecentItem::AvatarRole).isValid() && !item->data(ChatListRecentItem::AvatarRole).value<QString>().isNull()) { + avatarPath = item->data(ChatListRecentItem::AvatarRole).value<QString>(); + } + QIcon presenceIcon = item->data(ChatListRecentItem::PresenceIconRole).isValid() && !item->data(ChatListRecentItem::PresenceIconRole).value<QIcon>().isNull() + ? item->data(ChatListRecentItem::PresenceIconRole).value<QIcon>() + : QIcon(":/icons/offline.png"); + QString name = item->data(Qt::DisplayRole).toString(); + //qDebug() << "Avatar for " << name << " = " << avatarPath; + QString statusText = item->data(ChatListRecentItem::DetailTextRole).toString(); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, item->getChat().unreadCount); +} + } diff --git a/Swift/QtUI/ChatList/ChatListDelegate.h b/Swift/QtUI/ChatList/ChatListDelegate.h index f6c6c40..a898df4 100644 --- a/Swift/QtUI/ChatList/ChatListDelegate.h +++ b/Swift/QtUI/ChatList/ChatListDelegate.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -12,6 +12,7 @@ namespace Swift { class ChatListMUCItem; + class ChatListRecentItem; class ChatListDelegate : public QStyledItemDelegate { public: ChatListDelegate(); @@ -20,7 +21,9 @@ namespace Swift { void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; private: void paintMUC(QPainter* painter, const QStyleOptionViewItem& option, ChatListMUCItem* item) const; + void paintRecent(QPainter* painter, const QStyleOptionViewItem& option, ChatListRecentItem* item) const; QSize mucSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const; + QSize recentSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const; DelegateCommons common_; GroupItemDelegate* groupDelegate_; diff --git a/Swift/QtUI/ChatList/ChatListGroupItem.h b/Swift/QtUI/ChatList/ChatListGroupItem.h index cc4d4af..a1e479f 100644 --- a/Swift/QtUI/ChatList/ChatListGroupItem.h +++ b/Swift/QtUI/ChatList/ChatListGroupItem.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -13,8 +13,8 @@ namespace Swift { class ChatListGroupItem : public ChatListItem { public: - ChatListGroupItem(const QString& name, ChatListGroupItem* parent) : ChatListItem(parent), name_(name) {}; - void addItem(ChatListItem* item) {items_.push_back(item); qStableSort(items_.begin(), items_.end(), pointerItemLessThan);}; + ChatListGroupItem(const QString& name, ChatListGroupItem* parent, bool sorted = true) : ChatListItem(parent), name_(name), sorted_(sorted) {}; + void addItem(ChatListItem* item) {items_.push_back(item); if (sorted_) {qStableSort(items_.begin(), items_.end(), pointerItemLessThan);}}; void remove(int index) {items_.removeAt(index);}; int rowCount() {return items_.size();}; ChatListItem* item(int i) {return items_[i];}; @@ -30,5 +30,6 @@ namespace Swift { QString name_; QList<ChatListItem*> items_; + bool sorted_; }; } diff --git a/Swift/QtUI/ChatList/ChatListMUCItem.cpp b/Swift/QtUI/ChatList/ChatListMUCItem.cpp index e374ed5..68f9581 100644 --- a/Swift/QtUI/ChatList/ChatListMUCItem.cpp +++ b/Swift/QtUI/ChatList/ChatListMUCItem.cpp @@ -13,7 +13,7 @@ ChatListMUCItem::ChatListMUCItem(const MUCBookmark& bookmark, ChatListGroupItem* } -const MUCBookmark& ChatListMUCItem::getBookmark() { +const MUCBookmark& ChatListMUCItem::getBookmark() const { return bookmark_; } diff --git a/Swift/QtUI/ChatList/ChatListMUCItem.h b/Swift/QtUI/ChatList/ChatListMUCItem.h index 068f5d6..046d1d4 100644 --- a/Swift/QtUI/ChatList/ChatListMUCItem.h +++ b/Swift/QtUI/ChatList/ChatListMUCItem.h @@ -15,16 +15,16 @@ #include "Swift/QtUI/ChatList/ChatListItem.h" namespace Swift { - enum MUCItemRoles { - DetailTextRole = Qt::UserRole/*, - AvatarRole = Qt::UserRole + 1, - PresenceIconRole = Qt::UserRole + 2, - StatusShowTypeRole = Qt::UserRole + 3*/ - }; class ChatListMUCItem : public ChatListItem { public: + enum MUCItemRoles { + DetailTextRole = Qt::UserRole/*, + AvatarRole = Qt::UserRole + 1, + PresenceIconRole = Qt::UserRole + 2, + StatusShowTypeRole = Qt::UserRole + 3*/ + }; ChatListMUCItem(const MUCBookmark& bookmark, ChatListGroupItem* parent); - const MUCBookmark& getBookmark(); + const MUCBookmark& getBookmark() const; QVariant data(int role) const; private: MUCBookmark bookmark_; diff --git a/Swift/QtUI/ChatList/ChatListModel.cpp b/Swift/QtUI/ChatList/ChatListModel.cpp index ba7b766..681c1c2 100644 --- a/Swift/QtUI/ChatList/ChatListModel.cpp +++ b/Swift/QtUI/ChatList/ChatListModel.cpp @@ -1,22 +1,25 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "Swift/QtUI/ChatList/ChatListModel.h" +#include <Swift/QtUI/ChatList/ChatListModel.h> -#include "Swift/QtUI/ChatList/ChatListMUCItem.h" +#include <Swift/QtUI/ChatList/ChatListMUCItem.h> +#include <Swift/QtUI/ChatList/ChatListRecentItem.h> namespace Swift { ChatListModel::ChatListModel() { - root_ = new ChatListGroupItem("", NULL); + root_ = new ChatListGroupItem("", NULL, false); mucBookmarks_ = new ChatListGroupItem(tr("Bookmarked Rooms"), root_); + recents_ = new ChatListGroupItem(tr("Recent Chats"), root_, false); + root_->addItem(recents_); root_->addItem(mucBookmarks_); } -void ChatListModel::clear() { +void ChatListModel::clearBookmarks() { emit layoutAboutToBeChanged(); mucBookmarks_->clear(); emit layoutChanged(); @@ -43,6 +46,15 @@ void ChatListModel::removeMUCBookmark(const Swift::MUCBookmark& bookmark) { } } +void ChatListModel::setRecents(const std::list<ChatListWindow::Chat>& recents) { + emit layoutAboutToBeChanged(); + recents_->clear(); + foreach (const ChatListWindow::Chat chat, recents) { + recents_->addItem(new ChatListRecentItem(chat, recents_)); + } + emit layoutChanged(); +} + int ChatListModel::columnCount(const QModelIndex& /*parent*/) const { return 1; } diff --git a/Swift/QtUI/ChatList/ChatListModel.h b/Swift/QtUI/ChatList/ChatListModel.h index adde148..8e7828c 100644 --- a/Swift/QtUI/ChatList/ChatListModel.h +++ b/Swift/QtUI/ChatList/ChatListModel.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -11,9 +11,10 @@ #include <QAbstractItemModel> #include <QList> -#include "Swiften/MUC/MUCBookmark.h" +#include <Swiften/MUC/MUCBookmark.h> +#include <Swift/Controllers/UIInterfaces/ChatListWindow.h> -#include "Swift/QtUI/ChatList/ChatListGroupItem.h" +#include <Swift/QtUI/ChatList/ChatListGroupItem.h> namespace Swift { class ChatListModel : public QAbstractItemModel { @@ -28,9 +29,11 @@ namespace Swift { QModelIndex parent(const QModelIndex& index) const; int rowCount(const QModelIndex& parent = QModelIndex()) const; ChatListItem* getItemForIndex(const QModelIndex& index) const; - void clear(); + void clearBookmarks(); + void setRecents(const std::list<ChatListWindow::Chat>& recents); private: ChatListGroupItem* mucBookmarks_; + ChatListGroupItem* recents_; ChatListGroupItem* root_; }; diff --git a/Swift/QtUI/ChatList/ChatListRecentItem.cpp b/Swift/QtUI/ChatList/ChatListRecentItem.cpp new file mode 100644 index 0000000..6c9807f --- /dev/null +++ b/Swift/QtUI/ChatList/ChatListRecentItem.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/QtUI/ChatList/ChatListRecentItem.h> + +#include <Swift/QtUI/QtSwiftUtil.h> + +namespace Swift { +ChatListRecentItem::ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent) : ChatListItem(parent), chat_(chat) { + +} + +const ChatListWindow::Chat& ChatListRecentItem::getChat() const { + return chat_; +} + +QVariant ChatListRecentItem::data(int role) const { + switch (role) { + case Qt::DisplayRole: return P2QSTRING(chat_.chatName); + case DetailTextRole: return P2QSTRING(chat_.activity); + /*case Qt::TextColorRole: return textColor_; + case Qt::BackgroundColorRole: return backgroundColor_; + case Qt::ToolTipRole: return isContact() ? toolTipString() : QVariant(); + case StatusTextRole: return statusText_;*/ + case AvatarRole: return QVariant(QString(chat_.avatarPath.string().c_str())); + case PresenceIconRole: return getPresenceIcon(); + default: return QVariant(); + } +} + +QIcon ChatListRecentItem::getPresenceIcon() const { + QString iconString; + switch (chat_.statusType) { + case StatusShow::Online: iconString = "online";break; + case StatusShow::Away: iconString = "away";break; + case StatusShow::XA: iconString = "away";break; + case StatusShow::FFC: iconString = "online";break; + case StatusShow::DND: iconString = "dnd";break; + case StatusShow::None: iconString = "offline";break; + } + return QIcon(":/icons/" + iconString + ".png"); +} + +} diff --git a/Swift/QtUI/ChatList/ChatListRecentItem.h b/Swift/QtUI/ChatList/ChatListRecentItem.h new file mode 100644 index 0000000..4e7bc3e --- /dev/null +++ b/Swift/QtUI/ChatList/ChatListRecentItem.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QList> +#include <QIcon> + +#include <boost/shared_ptr.hpp> + +#include <Swiften/MUC/MUCBookmark.h> +#include <Swift/Controllers/UIInterfaces/ChatListWindow.h> + +#include <Swift/QtUI/ChatList/ChatListItem.h> + +namespace Swift { + class ChatListRecentItem : public ChatListItem { + public: + enum RecentItemRoles { + DetailTextRole = Qt::UserRole, + AvatarRole = Qt::UserRole + 1, + PresenceIconRole = Qt::UserRole + 2/*, + StatusShowTypeRole = Qt::UserRole + 3*/ + }; + ChatListRecentItem(const ChatListWindow::Chat& chat, ChatListGroupItem* parent); + const ChatListWindow::Chat& getChat() const; + QVariant data(int role) const; + private: + QIcon getPresenceIcon() const; + ChatListWindow::Chat chat_; + }; +} diff --git a/Swift/QtUI/ChatList/QtChatListWindow.cpp b/Swift/QtUI/ChatList/QtChatListWindow.cpp index b532cdb..9beb9dc 100644 --- a/Swift/QtUI/ChatList/QtChatListWindow.cpp +++ b/Swift/QtUI/ChatList/QtChatListWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -10,9 +10,11 @@ #include <QContextMenuEvent> #include "Swift/QtUI/ChatList/ChatListMUCItem.h" +#include "Swift/QtUI/ChatList/ChatListRecentItem.h" #include "Swift/QtUI/QtAddBookmarkWindow.h" #include "Swift/QtUI/QtEditBookmarkWindow.h" #include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" +#include "Swift/Controllers/UIEvents/RequestChatUIEvent.h" #include "Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h" #include "Swift/Controllers/UIEvents/RemoveMUCBookmarkUIEvent.h" #include "Swift/Controllers/UIEvents/EditMUCBookmarkUIEvent.h" @@ -68,19 +70,21 @@ void QtChatListWindow::setupContextMenus() { } void QtChatListWindow::handleItemActivated(const QModelIndex& index) { - if (!bookmarksEnabled_) { - return; - } ChatListItem* item = model_->getItemForIndex(index); - ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(item); - if (mucItem) { - boost::shared_ptr<UIEvent> event(new JoinMUCUIEvent(mucItem->getBookmark().getRoom(), mucItem->getBookmark().getNick())); - eventStream_->send(event); + if (ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(item)) { + if (bookmarksEnabled_) { + onMUCBookmarkActivated(mucItem->getBookmark()); + } + } + else if (ChatListRecentItem* recentItem = dynamic_cast<ChatListRecentItem*>(item)) { + if (!recentItem->getChat().isMUC || bookmarksEnabled_) { + onRecentActivated(recentItem->getChat()); + } } } -void QtChatListWindow::clear() { - model_->clear(); +void QtChatListWindow::clearBookmarks() { + model_->clearBookmarks(); } void QtChatListWindow::addMUCBookmark(const MUCBookmark& bookmark) { @@ -91,6 +95,14 @@ void QtChatListWindow::removeMUCBookmark(const MUCBookmark& bookmark) { model_->removeMUCBookmark(bookmark); } +void QtChatListWindow::setRecents(const std::list<ChatListWindow::Chat>& recents) { + model_->setRecents(recents); +} + +void QtChatListWindow::setUnreadCount(int unread) { + emit onCountUpdated(unread); +} + void QtChatListWindow::handleRemoveBookmark() { ChatListMUCItem* mucItem = dynamic_cast<ChatListMUCItem*>(contextMenuItem_); if (!mucItem) return; diff --git a/Swift/QtUI/ChatList/QtChatListWindow.h b/Swift/QtUI/ChatList/QtChatListWindow.h index 3a3e95f..8775c3e 100644 --- a/Swift/QtUI/ChatList/QtChatListWindow.h +++ b/Swift/QtUI/ChatList/QtChatListWindow.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -23,7 +23,12 @@ namespace Swift { void addMUCBookmark(const MUCBookmark& bookmark); void removeMUCBookmark(const MUCBookmark& bookmark); void setBookmarksEnabled(bool enabled); - void clear(); + void setRecents(const std::list<ChatListWindow::Chat>& recents); + void setUnreadCount(int unread); + void clearBookmarks(); + + signals: + void onCountUpdated(int count); private slots: void handleItemActivated(const QModelIndex&); void handleAddBookmark(); diff --git a/Swift/QtUI/ChatSnippet.h b/Swift/QtUI/ChatSnippet.h index 3aa5fcc..0d864c1 100644 --- a/Swift/QtUI/ChatSnippet.h +++ b/Swift/QtUI/ChatSnippet.h @@ -17,16 +17,16 @@ namespace Swift { public: ChatSnippet(bool appendToPrevious); virtual ~ChatSnippet(); - + virtual const QString& getContent() const = 0; virtual QString getContinuationElementID() const { return ""; } - boost::shared_ptr<ChatSnippet> getContinuationFallbackSnippet() {return continuationFallback_;} + boost::shared_ptr<ChatSnippet> getContinuationFallbackSnippet() const {return continuationFallback_;} bool getAppendToPrevious() const { return appendToPrevious_; } - + static QString escape(const QString& original) { QString result(original); result.replace("%message%", "%message%"); @@ -37,11 +37,12 @@ namespace Swift { return result; } + static QString timeToEscapedString(const QDateTime& time); + protected: void setContinuationFallbackSnippet(boost::shared_ptr<ChatSnippet> continuationFallback) { continuationFallback_ = continuationFallback; } - static QString timeToEscapedString(const QDateTime& time); private: bool appendToPrevious_; boost::shared_ptr<ChatSnippet> continuationFallback_; diff --git a/Swift/QtUI/EventViewer/QtEventWindow.h b/Swift/QtUI/EventViewer/QtEventWindow.h index 03f009b..a20e5ab 100644 --- a/Swift/QtUI/EventViewer/QtEventWindow.h +++ b/Swift/QtUI/EventViewer/QtEventWindow.h @@ -6,7 +6,7 @@ #pragma once -#include "boost/shared_ptr.hpp" +#include <boost/shared_ptr.hpp> #include <QTreeView> diff --git a/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h index 920aaae..a9eb74d 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h +++ b/Swift/QtUI/MUCSearch/MUCSearchRoomItem.h @@ -15,7 +15,7 @@ namespace Swift { MUCSearchRoomItem(const QString& node, MUCSearchServiceItem* parent); MUCSearchServiceItem* getParent(); QVariant data(int role); - QString getNode() {return node_;} + QString getNode() const {return node_;} private: MUCSearchServiceItem* parent_; QString node_; diff --git a/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h b/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h index 411727d..2a28922 100644 --- a/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h +++ b/Swift/QtUI/MUCSearch/MUCSearchServiceItem.h @@ -24,7 +24,7 @@ namespace Swift { default: return QVariant(); } } - QString getHost() {return jidString_;} + QString getHost() const {return jidString_;} private: QList<MUCSearchItem*> rooms_; QString jidString_; diff --git a/Swift/QtUI/QtAdHocCommandWindow.cpp b/Swift/QtUI/QtAdHocCommandWindow.cpp new file mode 100644 index 0000000..a3bb077 --- /dev/null +++ b/Swift/QtUI/QtAdHocCommandWindow.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2010-2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/QtUI/QtAdHocCommandWindow.h> + +#include <boost/bind.hpp> +#include <QBoxLayout> +#include <Swift/QtUI/QtFormWidget.h> +#include <Swiften/Elements/Command.h> + +namespace Swift { +QtAdHocCommandWindow::QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) : command_(command) { + + formWidget_ = NULL; + + setAttribute(Qt::WA_DeleteOnClose); + command->onNextStageReceived.connect(boost::bind(&QtAdHocCommandWindow::handleNextStageReceived, this, _1)); + command->onError.connect(boost::bind(&QtAdHocCommandWindow::handleError, this, _1)); + command->start(); + + QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this); + layout->setContentsMargins(0,0,0,0); + layout->setSpacing(2); + label_ = new QLabel(this); + layout->addWidget(label_); + QWidget* formContainer = new QWidget(this); + layout->addWidget(formContainer); + formLayout_ = new QBoxLayout(QBoxLayout::TopToBottom, formContainer); + QWidget* buttonsWidget = new QWidget(this); + layout->addWidget(buttonsWidget); + + QBoxLayout* buttonsLayout = new QBoxLayout(QBoxLayout::LeftToRight, buttonsWidget); + cancelButton_ = new QPushButton(tr("Cancel"), buttonsWidget); + buttonsLayout->addWidget(cancelButton_); + connect(cancelButton_, SIGNAL(clicked()), this, SLOT(handleCancelClicked())); + backButton_ = new QPushButton(tr("Back"), buttonsWidget); + buttonsLayout->addWidget(backButton_); + connect(backButton_, SIGNAL(clicked()), this, SLOT(handlePrevClicked())); + nextButton_ = new QPushButton(tr("Next"), buttonsWidget); + buttonsLayout->addWidget(nextButton_); + connect(nextButton_, SIGNAL(clicked()), this, SLOT(handleNextClicked())); + completeButton_ = new QPushButton(tr("Complete"), buttonsWidget); + buttonsLayout->addWidget(completeButton_); + connect(completeButton_, SIGNAL(clicked()), this, SLOT(handleCompleteClicked())); + nextButton_->setEnabled(false); + backButton_->setEnabled(false); + completeButton_->setEnabled(false); + actions_[Command::Next] = nextButton_; + actions_[Command::Prev] = backButton_; + actions_[Command::Complete] = completeButton_; + actions_[Command::Cancel] = cancelButton_; + show(); +} + +QtAdHocCommandWindow::~QtAdHocCommandWindow() { + +} + +void QtAdHocCommandWindow::handleCancelClicked() { + command_->cancel(); +} + +void QtAdHocCommandWindow::handlePrevClicked() { + command_->goBack(); +} + +void QtAdHocCommandWindow::handleNextClicked() { + command_->goNext(formWidget_ ? formWidget_->getCompletedForm() : Form::ref()); +} + +void QtAdHocCommandWindow::handleCompleteClicked() { + command_->complete(formWidget_ ? formWidget_->getCompletedForm() : Form::ref()); +} + +void QtAdHocCommandWindow::handleNextStageReceived(Command::ref command) { + if (command->getForm()) { + setForm(command->getForm()); + } else { + setNoForm(); + } + QString notes; + foreach (Command::Note note, command->getNotes()) { + if (!notes.isEmpty()) { + notes += "\n"; + QString qNote(note.note.c_str()); + switch (note.type) { + case Command::Note::Error: notes += tr("Error: %1").arg(qNote); break; + case Command::Note::Warn: notes += tr("Warning: %1").arg(qNote); break; + case Command::Note::Info: notes += qNote; break; + } + } + } + label_->setText(notes); + setAvailableActions(command); +} + +void QtAdHocCommandWindow::handleError(ErrorPayload::ref /*error*/) { + nextButton_->setEnabled(false); + backButton_->setEnabled(false); + completeButton_->setEnabled(false); + label_->setText(tr("Error executing command")); +} + +void QtAdHocCommandWindow::setForm(Form::ref form) { + delete formWidget_; + formWidget_ = new QtFormWidget(form, this); + formLayout_->addWidget(formWidget_); +} + +void QtAdHocCommandWindow::setNoForm() { + delete formWidget_; +} + +typedef std::pair<Command::Action, QPushButton*> ActionButton; + +void QtAdHocCommandWindow::setAvailableActions(Command::ref /*commandResult*/) { + foreach (ActionButton pair, actions_) { + OutgoingAdHocCommandSession::ActionState state = command_->getActionState(pair.first); + if (state & OutgoingAdHocCommandSession::Present) { + pair.second->show(); + } + else { + pair.second->hide(); + } + if (state & OutgoingAdHocCommandSession::Enabled) { + pair.second->setEnabled(true); + } + else { + pair.second->setEnabled(false); + } + } +} + +} diff --git a/Swift/QtUI/QtAdHocCommandWindow.h b/Swift/QtUI/QtAdHocCommandWindow.h new file mode 100644 index 0000000..adeb3e6 --- /dev/null +++ b/Swift/QtUI/QtAdHocCommandWindow.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2010-2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QWidget> +#include <QPushButton> +#include <QLabel> + +#include <Swift/Controllers/UIInterfaces/AdHocCommandWindow.h> +#include <Swiften/AdHoc/OutgoingAdHocCommandSession.h> + +class QBoxLayout; + +namespace Swift { + class QtFormWidget; + class QtAdHocCommandWindow : public QWidget, public AdHocCommandWindow { + Q_OBJECT + public: + QtAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); + virtual ~QtAdHocCommandWindow(); + private: + void handleNextStageReceived(Command::ref command); + void handleError(ErrorPayload::ref error); + void setForm(Form::ref); + void setNoForm(); + void setAvailableActions(Command::ref commandResult); + private slots: + void handleCancelClicked(); + void handlePrevClicked(); + void handleNextClicked(); + void handleCompleteClicked(); + private: + boost::shared_ptr<OutgoingAdHocCommandSession> command_; + QtFormWidget* formWidget_; + QBoxLayout* formLayout_; + Form::ref form_; + QLabel* label_; + QPushButton* backButton_; + QPushButton* nextButton_; + QPushButton* completeButton_; + QPushButton* cancelButton_; + std::map<Command::Action, QPushButton*> actions_; + }; +} diff --git a/Swift/QtUI/QtAvatarWidget.cpp b/Swift/QtUI/QtAvatarWidget.cpp index 3e3aa69..1efb787 100644 --- a/Swift/QtUI/QtAvatarWidget.cpp +++ b/Swift/QtUI/QtAvatarWidget.cpp @@ -47,8 +47,8 @@ void QtAvatarWidget::setAvatar(const ByteArray& data, const std::string& type) { this->type = type; QImage image; - if (!data.isEmpty()) { - image.loadFromData(reinterpret_cast<const uchar*>(data.getData()), data.getSize()); + if (!data.empty()) { + image.loadFromData(reinterpret_cast<const uchar*>(vecptr(data)), data.size()); } if (image.isNull()) { @@ -81,10 +81,10 @@ void QtAvatarWidget::mousePressEvent(QMouseEvent* event) { QString fileName = QFileDialog::getOpenFileName(this, tr("Select picture"), "", tr("Image Files (*.png *.jpg *.gif)")); if (!fileName.isEmpty()) { ByteArray data; - data.readFromFile(Q2PSTRING(fileName)); + readByteArrayFromFile(data, Q2PSTRING(fileName)); QBuffer buffer; - buffer.setData(reinterpret_cast<const char*>(data.getData()), data.getSize()); + buffer.setData(reinterpret_cast<const char*>(vecptr(data)), data.size()); buffer.open(QIODevice::ReadOnly); QString type = QImageReader::imageFormat(&buffer).toLower(); if (!type.isEmpty()) { diff --git a/Swift/QtUI/QtBookmarkDetailWindow.ui b/Swift/QtUI/QtBookmarkDetailWindow.ui index a77ac76..4a37b2f 100644 --- a/Swift/QtUI/QtBookmarkDetailWindow.ui +++ b/Swift/QtUI/QtBookmarkDetailWindow.ui @@ -90,7 +90,7 @@ <item row="4" column="1"> <widget class="QCheckBox" name="autojoin_"> <property name="text"> - <string>Join automatically</string> + <string>Enter automatically</string> </property> <property name="checked"> <bool>true</bool> diff --git a/Swift/QtUI/QtChatTabs.cpp b/Swift/QtUI/QtChatTabs.cpp index 25c7ca2..249080b 100644 --- a/Swift/QtUI/QtChatTabs.cpp +++ b/Swift/QtUI/QtChatTabs.cpp @@ -7,6 +7,10 @@ #include "QtChatTabs.h" #include <algorithm> +#include <vector> + +#include <Swift/Controllers/ChatMessageSummarizer.h> +#include <Swift/QtUI/QtSwiftUtil.h> #include <QCloseEvent> #include <QDesktopWidget> @@ -236,16 +240,18 @@ void QtChatTabs::handleTabTitleUpdated(QWidget* widget) { default : tabTextColor = QColor(); } tabs_->tabBar()->setTabTextColor(index, tabTextColor); - int unread = 0; + + std::vector<std::pair<std::string, int> > unreads; for (int i = 0; i < tabs_->count(); i++) { QtTabbable* tab = qobject_cast<QtTabbable*>(tabs_->widget(i)); if (tab) { - unread += tab->getCount(); + unreads.push_back(std::pair<std::string, int>(Q2PSTRING(tab->windowTitle()), tab->getCount())); } } - QtTabbable* current = qobject_cast<QtTabbable*>(tabs_->currentWidget()); - setWindowTitle(unread > 0 ? QString("(%1) %2").arg(unread).arg(current->windowTitle()) : current->windowTitle()); + std::string current(Q2PSTRING(qobject_cast<QtTabbable*>(tabs_->currentWidget())->windowTitle())); + ChatMessageSummarizer summary; + setWindowTitle(summary.getSummary(current, unreads).c_str()); } void QtChatTabs::flash() { diff --git a/Swift/QtUI/QtChatTheme.cpp b/Swift/QtUI/QtChatTheme.cpp index afcf665..1d7a970 100644 --- a/Swift/QtUI/QtChatTheme.cpp +++ b/Swift/QtUI/QtChatTheme.cpp @@ -60,7 +60,7 @@ QtChatTheme::QtChatTheme(const QString& themePath) : qrc_(themePath.isEmpty()), } -QString QtChatTheme::getBase() { +QString QtChatTheme::getBase() const { return qrc_ ? "qrc" + themePath_ : "file://" + themePath_; } diff --git a/Swift/QtUI/QtChatTheme.h b/Swift/QtUI/QtChatTheme.h index 199c66d..c6b02a0 100644 --- a/Swift/QtUI/QtChatTheme.h +++ b/Swift/QtUI/QtChatTheme.h @@ -13,23 +13,23 @@ namespace Swift { class QtChatTheme { public: QtChatTheme(const QString& themePath); - QString getHeader() {return fileContents_[Header];}; - QString getFooter() {return fileContents_[Footer];}; - QString getContent() {return fileContents_[Content];}; - QString getStatus() {return fileContents_[Status];}; - QString getTopic() {return fileContents_[Topic];}; - QString getFileTransferRequest() {return fileContents_[FileTransferRequest];}; - QString getIncomingContent() {return fileContents_[IncomingContent];}; - QString getIncomingNextContent() {return fileContents_[IncomingNextContent];}; - QString getIncomingContext() {return fileContents_[IncomingContext];}; - QString getIncomingNextContext() {return fileContents_[IncomingNextContext];}; - QString getOutgoingContent() {return fileContents_[OutgoingContent];}; - QString getOutgoingNextContent() {return fileContents_[OutgoingNextContent];}; - QString getOutgoingContext() {return fileContents_[OutgoingContext];}; - QString getOutgoingNextContext() {return fileContents_[OutgoingNextContext];}; - QString getTemplate() {return fileContents_[Template];} - QString getMainCSS() {return fileContents_[MainCSS];} - QString getBase(); + QString getHeader() const {return fileContents_[Header];}; + QString getFooter() const {return fileContents_[Footer];}; + QString getContent() const {return fileContents_[Content];}; + QString getStatus() const {return fileContents_[Status];}; + QString getTopic() const {return fileContents_[Topic];}; + QString getFileTransferRequest() const {return fileContents_[FileTransferRequest];}; + QString getIncomingContent() const {return fileContents_[IncomingContent];}; + QString getIncomingNextContent() const {return fileContents_[IncomingNextContent];}; + QString getIncomingContext() const {return fileContents_[IncomingContext];}; + QString getIncomingNextContext() const {return fileContents_[IncomingNextContext];}; + QString getOutgoingContent() const {return fileContents_[OutgoingContent];}; + QString getOutgoingNextContent() const {return fileContents_[OutgoingNextContent];}; + QString getOutgoingContext() const {return fileContents_[OutgoingContext];}; + QString getOutgoingNextContext() const {return fileContents_[OutgoingNextContext];}; + QString getTemplate() const {return fileContents_[Template];} + QString getMainCSS() const {return fileContents_[MainCSS];} + QString getBase() const; private: enum files {Header = 0, Footer, Content, Status, Topic, FileTransferRequest, IncomingContent, IncomingNextContent, IncomingContext, IncomingNextContext, OutgoingContent, OutgoingNextContent, OutgoingContext, OutgoingNextContext, Template, MainCSS, TemplateDefault, EndMarker}; diff --git a/Swift/QtUI/QtChatView.cpp b/Swift/QtUI/QtChatView.cpp index 521b072..c533525 100644 --- a/Swift/QtUI/QtChatView.cpp +++ b/Swift/QtUI/QtChatView.cpp @@ -24,7 +24,7 @@ namespace Swift { -QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent) { +QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent), fontSizeSteps_(0) { theme_ = theme; QVBoxLayout* mainLayout = new QVBoxLayout(this); @@ -35,6 +35,8 @@ QtChatView::QtChatView(QtChatTheme* theme, QWidget* parent) : QWidget(parent) { connect(webView_, SIGNAL(loadFinished(bool)), SLOT(handleViewLoadFinished(bool))); connect(webView_, SIGNAL(gotFocus()), SIGNAL(gotFocus())); connect(webView_, SIGNAL(clearRequested()), SLOT(handleClearRequested())); + connect(webView_, SIGNAL(fontGrowRequested()), SLOT(increaseFontSize())); + connect(webView_, SIGNAL(fontShrinkRequested()), SLOT(decreaseFontSize())); #ifdef Q_WS_X11 /* To give a border on Linux, where it looks bad without */ QStackedWidget* stack = new QStackedWidget(this); @@ -77,13 +79,14 @@ void QtChatView::addMessage(boost::shared_ptr<ChatSnippet> snippet) { if (viewReady_) { addToDOM(snippet); } else { - queuedSnippets_.append(snippet); + /* If this asserts, the previous queuing code was necessary and should be reinstated */ + assert(false); } } QWebElement QtChatView::snippetToDOM(boost::shared_ptr<ChatSnippet> snippet) { QWebElement newElement = newInsertPoint_.clone(); - newElement.setInnerXml(snippet->getContent()); /* FIXME: Outer, surely? */ + newElement.setInnerXml(snippet->getContent()); Q_ASSERT(!newElement.isNull()); return newElement; } @@ -103,12 +106,31 @@ void QtChatView::addToDOM(boost::shared_ptr<ChatSnippet> snippet) { newInsertPoint_.prependOutside(newElement); } lastElement_ = newElement; - //qApp->processEvents(); + if (fontSizeSteps_ != 0) { + double size = 1.0 + 0.2 * fontSizeSteps_; + QString sizeString(QString().setNum(size, 'g', 3) + "em"); + const QWebElementCollection spans = lastElement_.findAll("span"); + foreach (QWebElement span, spans) { + span.setStyleProperty("font-size", sizeString); + } + } +} + +void QtChatView::addLastSeenLine() { + if (lineSeparator_.isNull()) { + lineSeparator_ = newInsertPoint_.clone(); + lineSeparator_.setInnerXml(QString("<hr/>")); + newInsertPoint_.prependOutside(lineSeparator_); + } + else { + QWebElement lineSeparatorC = lineSeparator_.clone(); + lineSeparatorC.removeFromDocument(); + } + newInsertPoint_.prependOutside(lineSeparator_); } void QtChatView::replaceLastMessage(const QString& newMessage) { assert(viewReady_); - /* FIXME: must be queued? */ rememberScrolledToBottom(); assert(!lastElement_.isNull()); QWebElement replace = lastElement_.findFirst("span.swift_message"); @@ -125,6 +147,25 @@ void QtChatView::replaceLastMessage(const QString& newMessage, const QString& no replace.setInnerXml(ChatSnippet::escape(note)); } +QString QtChatView::getLastSentMessage() { + return lastElement_.toPlainText(); +} + +void QtChatView::replaceMessage(const QString& newMessage, const QString& id, const QDateTime& editTime) { + rememberScrolledToBottom(); + QWebElement message = document_.findFirst("#" + id); + if (!message.isNull()) { + QWebElement replaceContent = message.findFirst("span.swift_message"); + assert(!replaceContent.isNull()); + QString old = replaceContent.toOuterXml(); + replaceContent.setInnerXml(ChatSnippet::escape(newMessage)); + QWebElement replaceTime = message.findFirst("span.swift_time"); + assert(!replaceTime.isNull()); + old = replaceTime.toOuterXml(); + replaceTime.setInnerXml(ChatSnippet::escape(tr("%1 edited").arg(ChatSnippet::timeToEscapedString(editTime)))); + } +} + void QtChatView::copySelectionToClipboard() { if (!webPage_->selectedText().isEmpty()) { webPage_->triggerAction(QWebPage::Copy); @@ -160,17 +201,34 @@ void QtChatView::handleLinkClicked(const QUrl& url) { QDesktopServices::openUrl(url); } -void QtChatView::addQueuedSnippets() { - for (int i = 0; i < queuedSnippets_.count(); i++) { - addToDOM(queuedSnippets_[i]); - } - queuedSnippets_.clear(); -} - void QtChatView::handleViewLoadFinished(bool ok) { Q_ASSERT(ok); viewReady_ = true; - addQueuedSnippets(); +} + +void QtChatView::increaseFontSize(int numSteps) { + //qDebug() << "Increasing"; + fontSizeSteps_ += numSteps; + emit fontResized(fontSizeSteps_); +} + +void QtChatView::decreaseFontSize() { + fontSizeSteps_--; + if (fontSizeSteps_ < 0) { + fontSizeSteps_ = 0; + } + emit fontResized(fontSizeSteps_); +} + +void QtChatView::resizeFont(int fontSizeSteps) { + fontSizeSteps_ = fontSizeSteps; + double size = 1.0 + 0.2 * fontSizeSteps_; + QString sizeString(QString().setNum(size, 'g', 3) + "em"); + //qDebug() << "Setting to " << sizeString; + const QWebElementCollection spans = document_.findAll("span"); + foreach (QWebElement span, spans) { + span.setStyleProperty("font-size", sizeString); + } } void QtChatView::resetView() { diff --git a/Swift/QtUI/QtChatView.h b/Swift/QtUI/QtChatView.h index 58b33df..eda7e42 100644 --- a/Swift/QtUI/QtChatView.h +++ b/Swift/QtUI/QtChatView.h @@ -26,15 +26,18 @@ namespace Swift { Q_OBJECT public: QtChatView(QtChatTheme* theme, QWidget* parent); - void addMessage(boost::shared_ptr<ChatSnippet> snippet); + void addLastSeenLine(); void replaceLastMessage(const QString& newMessage); void replaceLastMessage(const QString& newMessage, const QString& note); + void replaceMessage(const QString& newMessage, const QString& id, const QDateTime& time); void rememberScrolledToBottom(); void setAckXML(const QString& id, const QString& xml); + QString getLastSentMessage(); signals: void gotFocus(); + void fontResized(int); public slots: void copySelectionToClipboard(); @@ -42,6 +45,9 @@ namespace Swift { void handleLinkClicked(const QUrl&); void handleKeyPressEvent(QKeyEvent* event); void resetView(); + void increaseFontSize(int numSteps = 1); + void decreaseFontSize(); + void resizeFont(int fontSizeSteps); private slots: void handleViewLoadFinished(bool); @@ -51,7 +57,6 @@ namespace Swift { private: void headerEncode(); void messageEncode(); - void addQueuedSnippets(); void addToDOM(boost::shared_ptr<ChatSnippet> snippet); QWebElement snippetToDOM(boost::shared_ptr<ChatSnippet> snippet); @@ -59,10 +64,10 @@ namespace Swift { bool isAtBottom_; QtWebView* webView_; QWebPage* webPage_; - QList<boost::shared_ptr<ChatSnippet> > queuedSnippets_; - + int fontSizeSteps_; QtChatTheme* theme_; QWebElement newInsertPoint_; + QWebElement lineSeparator_; QWebElement lastElement_; QWebElement document_; }; diff --git a/Swift/QtUI/QtChatWindow.cpp b/Swift/QtUI/QtChatWindow.cpp index 1a909fd..32c3067 100644 --- a/Swift/QtUI/QtChatWindow.cpp +++ b/Swift/QtUI/QtChatWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -13,10 +13,12 @@ #include "MessageSnippet.h" #include "SystemMessageSnippet.h" #include "QtTextEdit.h" +#include "QtSettingsProvider.h" #include "QtScaledAvatarCache.h" #include "SwifTools/TabComplete.h" +#include <QLabel> #include <QApplication> #include <QBoxLayout> #include <QCloseEvent> @@ -35,25 +37,27 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt inputEnabled_ = true; completer_ = NULL; theme_ = theme; + isCorrection_ = false; updateTitleWithUnreadCount(); + QtSettingsProvider settings; QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, this); layout->setContentsMargins(0,0,0,0); layout->setSpacing(2); - - QSplitter *logRosterSplitter = new QSplitter(this); - logRosterSplitter->setAutoFillBackground(true); - layout->addWidget(logRosterSplitter); + logRosterSplitter_ = new QSplitter(this); + logRosterSplitter_->setAutoFillBackground(true); + layout->addWidget(logRosterSplitter_); messageLog_ = new QtChatView(theme, this); - logRosterSplitter->addWidget(messageLog_); + logRosterSplitter_->addWidget(messageLog_); treeWidget_ = new QtTreeWidget(eventStream_); treeWidget_->setEditable(false); treeWidget_->hide(); - logRosterSplitter->addWidget(treeWidget_); - logRosterSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + logRosterSplitter_->addWidget(treeWidget_); + logRosterSplitter_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + connect(logRosterSplitter_, SIGNAL(splitterMoved(int, int)), this, SLOT(handleSplitterMoved(int, int))); QWidget* midBar = new QWidget(this); layout->addWidget(midBar); @@ -69,10 +73,17 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt labelsWidget_->setSizeAdjustPolicy(QComboBox::AdjustToContents); midBarLayout->addWidget(labelsWidget_,0); + QHBoxLayout* inputBarLayout = new QHBoxLayout(); + inputBarLayout->setContentsMargins(0,0,0,0); + inputBarLayout->setSpacing(2); input_ = new QtTextEdit(this); input_->setAcceptRichText(false); - layout->addWidget(input_); - + inputBarLayout->addWidget(input_); + correctingLabel_ = new QLabel(tr("Correcting"), this); + inputBarLayout->addWidget(correctingLabel_); + correctingLabel_->hide(); + layout->addLayout(inputBarLayout); + inputClearing_ = false; contactIsTyping_ = false; @@ -80,18 +91,23 @@ QtChatWindow::QtChatWindow(const QString &contact, QtChatTheme* theme, UIEventSt connect(input_, SIGNAL(returnPressed()), this, SLOT(returnPressed())); connect(input_, SIGNAL(textChanged()), this, SLOT(handleInputChanged())); setFocusProxy(input_); - logRosterSplitter->setFocusProxy(input_); + logRosterSplitter_->setFocusProxy(input_); midBar->setFocusProxy(input_); messageLog_->setFocusProxy(input_); connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(qAppFocusChanged(QWidget*, QWidget*))); connect(messageLog_, SIGNAL(gotFocus()), input_, SLOT(setFocus())); resize(400,300); + connect(messageLog_, SIGNAL(fontResized(int)), this, SIGNAL(fontResized(int))); } QtChatWindow::~QtChatWindow() { } +void QtChatWindow::handleFontResized(int fontSizeSteps) { + messageLog_->resizeFont(fontSizeSteps); +} + void QtChatWindow::setTabComplete(TabComplete* completer) { completer_ = completer; } @@ -118,11 +134,47 @@ void QtChatWindow::handleKeyPressEvent(QKeyEvent* event) { emit requestActiveTab(); } else if (key == Qt::Key_Tab) { tabComplete(); + } else if ((key == Qt::Key_Up) && input_->toPlainText().isEmpty() && !(lastSentMessage_.isEmpty())) { + beginCorrection(); + } else if (key == Qt::Key_Down && isCorrection_ && input_->textCursor().atBlockEnd()) { + cancelCorrection(); + } else if (key == Qt::Key_Down || key == Qt::Key_Up) { + /* Drop */ } else { messageLog_->handleKeyPressEvent(event); } } +void QtChatWindow::beginCorrection() { + QTextCursor cursor = input_->textCursor(); + cursor.select(QTextCursor::Document); + cursor.beginEditBlock(); + cursor.insertText(QString(lastSentMessage_)); + cursor.endEditBlock(); + isCorrection_ = true; + correctingLabel_->show(); +} + +void QtChatWindow::cancelCorrection() { + QTextCursor cursor = input_->textCursor(); + cursor.select(QTextCursor::Document); + cursor.removeSelectedText(); + isCorrection_ = false; + correctingLabel_->hide(); +} + +QByteArray QtChatWindow::getSplitterState() { + return logRosterSplitter_->saveState(); +} + +void QtChatWindow::handleChangeSplitterState(QByteArray state) { + logRosterSplitter_->restoreState(state); +} + +void QtChatWindow::handleSplitterMoved(int, int) { + emit splitterMoved(); +} + void QtChatWindow::tabComplete() { if (!completer_) { return; @@ -150,7 +202,7 @@ void QtChatWindow::tabComplete() { } void QtChatWindow::setRosterModel(Roster* roster) { - treeWidget_->setRosterModel(roster); + treeWidget_->setRosterModel(roster); } void QtChatWindow::setAvailableSecurityLabels(const std::vector<SecurityLabelsCatalog::Item>& labels) { @@ -204,10 +256,13 @@ void QtChatWindow::qAppFocusChanged(QWidget *old, QWidget *now) { Q_UNUSED(old); Q_UNUSED(now); if (isWidgetSelected()) { + lastLineTracker_.setHasFocus(true); input_->setFocus(); onAllMessagesRead(); } - + else { + lastLineTracker_.setHasFocus(false); + } } void QtChatWindow::setInputEnabled(bool enabled) { @@ -236,7 +291,7 @@ void QtChatWindow::setContactChatState(ChatState::ChatStateType state) { QtTabbable::AlertType QtChatWindow::getWidgetAlertState() { if (contactIsTyping_) { return ImpendingActivity; - } + } if (unreadCount_ > 0) { return WaitingActivity; } @@ -265,7 +320,6 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri if (isWidgetSelected()) { onAllMessagesRead(); } - QString scaledAvatarPath = QtScaledAvatarCache(32).getScaledAvatarPath(avatarPath.c_str()); QString htmlString; @@ -281,6 +335,12 @@ std::string QtChatWindow::addMessage(const std::string &message, const std::stri htmlString += styleSpanStart + messageHTML + styleSpanEnd; bool appendToPrevious = !previousMessageWasSystem_ && !previousMessageWasPresence_ && ((senderIsSelf && previousMessageWasSelf_) || (!senderIsSelf && !previousMessageWasSelf_ && previousSenderName_ == P2QSTRING(senderName))); + if (lastLineTracker_.getShouldMoveLastLine()) { + /* should this be queued? */ + messageLog_->addLastSeenLine(); + /* if the line is added we should break the snippet */ + appendToPrevious = false; + } QString qAvatarPath = scaledAvatarPath.isEmpty() ? "qrc:/icons/avatar.png" : QUrl::fromLocalFile(scaledAvatarPath).toEncoded(); std::string id = id_.generateID(); messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new MessageSnippet(htmlString, Qt::escape(P2QSTRING(senderName)), B2QDATE(time), qAvatarPath, senderIsSelf, appendToPrevious, theme_, P2QSTRING(id)))); @@ -321,7 +381,7 @@ void QtChatWindow::addErrorMessage(const std::string& errorMessage) { QString errorMessageHTML(Qt::escape(P2QSTRING(errorMessage))); errorMessageHTML.replace("\n","<br/>"); - messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet(QString("<span class=\"error\">" + tr("Couldn't send message: %1") + "</span>").arg(errorMessageHTML), QDateTime::currentDateTime(), false, theme_))); + messageLog_->addMessage(boost::shared_ptr<ChatSnippet>(new SystemMessageSnippet("<span class=\"error\">" + errorMessageHTML + "</span>", QDateTime::currentDateTime(), false, theme_))); previousMessageWasSelf_ = false; previousMessageWasSystem_ = true; @@ -343,6 +403,15 @@ void QtChatWindow::addSystemMessage(const std::string& message) { previousMessageWasPresence_ = false; } +void QtChatWindow::replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time) { + if (!id.empty()) { + QString messageHTML(Qt::escape(P2QSTRING(message))); + messageHTML = P2QSTRING(Linkify::linkify(Q2PSTRING(messageHTML))); + messageHTML.replace("\n","<br/>"); + messageLog_->replaceMessage(messageHTML, P2QSTRING(id), B2QDATE(time)); + } +} + void QtChatWindow::addPresenceMessage(const std::string& message) { if (isWidgetSelected()) { onAllMessagesRead(); @@ -364,9 +433,11 @@ void QtChatWindow::returnPressed() { return; } messageLog_->scrollToBottom(); - onSendMessageRequest(Q2PSTRING(input_->toPlainText())); + lastSentMessage_ = QString(input_->toPlainText()); + onSendMessageRequest(Q2PSTRING(input_->toPlainText()), isCorrection_); inputClearing_ = true; input_->clear(); + cancelCorrection(); inputClearing_ = false; } @@ -382,7 +453,9 @@ void QtChatWindow::handleInputChanged() { } void QtChatWindow::show() { - QWidget::show(); + if (parentWidget() == NULL) { + QWidget::show(); + } emit windowOpening(); } @@ -399,7 +472,7 @@ void QtChatWindow::resizeEvent(QResizeEvent*) { } void QtChatWindow::moveEvent(QMoveEvent*) { - emit geometryChanged(); + emit geometryChanged(); } void QtChatWindow::replaceLastMessage(const std::string& message) { diff --git a/Swift/QtUI/QtChatWindow.h b/Swift/QtUI/QtChatWindow.h index 910019b..78d8f91 100644 --- a/Swift/QtUI/QtChatWindow.h +++ b/Swift/QtUI/QtChatWindow.h @@ -1,21 +1,24 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#ifndef SWIFT_QtChatWindow_H -#define SWIFT_QtChatWindow_H +#pragma once #include "Swift/Controllers/UIInterfaces/ChatWindow.h" #include "QtTabbable.h" +#include "SwifTools/LastLineTracker.h" + #include "Swiften/Base/IDGenerator.h" class QTextEdit; class QLineEdit; class QComboBox; +class QLabel; +class QSplitter; namespace Swift { class QtChatView; @@ -35,6 +38,7 @@ namespace Swift { void addSystemMessage(const std::string& message); void addPresenceMessage(const std::string& message); void addErrorMessage(const std::string& errorMessage); + void replaceMessage(const std::string& message, const std::string& id, const boost::posix_time::ptime& time); void show(); void activate(); void setUnreadMessageCount(int count); @@ -54,9 +58,16 @@ namespace Swift { void replaceLastMessage(const std::string& message); void setAckState(const std::string& id, AckState state); void flash(); + QByteArray getSplitterState(); + + public slots: + void handleChangeSplitterState(QByteArray state); + void handleFontResized(int fontSizeSteps); signals: void geometryChanged(); + void splitterMoved(); + void fontResized(int); protected slots: void qAppFocusChanged(QWidget* old, QWidget* now); @@ -71,22 +82,29 @@ namespace Swift { void returnPressed(); void handleInputChanged(); void handleKeyPressEvent(QKeyEvent* event); + void handleSplitterMoved(int pos, int index); private: void updateTitleWithUnreadCount(); void tabComplete(); + void beginCorrection(); + void cancelCorrection(); std::string addMessage(const std::string &message, const std::string &senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& avatarPath, const QString& style, const boost::posix_time::ptime& time); int unreadCount_; bool contactIsTyping_; + LastLineTracker lastLineTracker_; QString contact_; + QString lastSentMessage_; QtChatView* messageLog_; QtChatTheme* theme_; QtTextEdit* input_; QComboBox* labelsWidget_; QtTreeWidget* treeWidget_; + QLabel* correctingLabel_; TabComplete* completer_; std::vector<SecurityLabelsCatalog::Item> availableLabels_; + bool isCorrection_; bool previousMessageWasSelf_; bool previousMessageWasSystem_; bool previousMessageWasPresence_; @@ -95,7 +113,6 @@ namespace Swift { UIEventStream* eventStream_; bool inputEnabled_; IDGenerator id_; + QSplitter *logRosterSplitter_; }; } - -#endif diff --git a/Swift/QtUI/QtChatWindowFactory.cpp b/Swift/QtUI/QtChatWindowFactory.cpp index d146474..4943c0e 100644 --- a/Swift/QtUI/QtChatWindowFactory.cpp +++ b/Swift/QtUI/QtChatWindowFactory.cpp @@ -16,6 +16,10 @@ namespace Swift { + +static const QString SPLITTER_STATE = "mucSplitterState"; +static const QString CHAT_TABS_GEOMETRY = "chatTabsGeometry"; + QtChatWindowFactory::QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider* settings, QtChatTabs* tabs, const QString& themePath) : themePath_(themePath) { settings_ = settings; tabs_ = tabs; @@ -23,7 +27,7 @@ QtChatWindowFactory::QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider if (splitter) { splitter->addWidget(tabs_); } else if (tabs_) { - QVariant chatTabsGeometryVariant = settings_->getQSettings()->value("chatTabsGeometry"); + QVariant chatTabsGeometryVariant = settings_->getQSettings()->value(CHAT_TABS_GEOMETRY); if (chatTabsGeometryVariant.isValid()) { tabs_->restoreGeometry(chatTabsGeometryVariant.toByteArray()); } @@ -43,11 +47,20 @@ ChatWindow* QtChatWindowFactory::createChatWindow(const JID &contact,UIEventStre theme_ = new QtChatTheme(""); /* Use the inbuilt theme */ } } + QtChatWindow *chatWindow = new QtChatWindow(P2QSTRING(contact.toString()), theme_, eventStream); + connect(chatWindow, SIGNAL(splitterMoved()), this, SLOT(handleSplitterMoved())); + connect(this, SIGNAL(changeSplitterState(QByteArray)), chatWindow, SLOT(handleChangeSplitterState(QByteArray))); + + QVariant splitterState = settings_->getQSettings()->value(SPLITTER_STATE); + if(splitterState.isValid()) { + chatWindow->handleChangeSplitterState(splitterState.toByteArray()); + } + if (tabs_) { tabs_->addTab(chatWindow); } else { - QVariant chatGeometryVariant = settings_->getQSettings()->value("chatTabsGeometry"); + QVariant chatGeometryVariant = settings_->getQSettings()->value(CHAT_TABS_GEOMETRY); if (chatGeometryVariant.isValid()) { chatWindow->restoreGeometry(chatGeometryVariant.toByteArray()); } @@ -57,7 +70,13 @@ ChatWindow* QtChatWindowFactory::createChatWindow(const JID &contact,UIEventStre } void QtChatWindowFactory::handleWindowGeometryChanged() { - settings_->getQSettings()->setValue("chatTabsGeometry", qobject_cast<QWidget*>(sender())->saveGeometry()); + settings_->getQSettings()->setValue(CHAT_TABS_GEOMETRY, qobject_cast<QWidget*>(sender())->saveGeometry()); +} + +void QtChatWindowFactory::handleSplitterMoved() { + QByteArray splitterState = qobject_cast<QtChatWindow*>(sender())->getSplitterState(); + settings_->getQSettings()->setValue(SPLITTER_STATE, QVariant(splitterState)); + emit changeSplitterState(splitterState); } } diff --git a/Swift/QtUI/QtChatWindowFactory.h b/Swift/QtUI/QtChatWindowFactory.h index 0d47854..f3e8956 100644 --- a/Swift/QtUI/QtChatWindowFactory.h +++ b/Swift/QtUI/QtChatWindowFactory.h @@ -22,8 +22,11 @@ namespace Swift { QtChatWindowFactory(QSplitter* splitter, QtSettingsProvider* settings, QtChatTabs* tabs, const QString& themePath); ~QtChatWindowFactory(); ChatWindow* createChatWindow(const JID &contact, UIEventStream* eventStream); + signals: + void changeSplitterState(QByteArray); private slots: void handleWindowGeometryChanged(); + void handleSplitterMoved(); private: QString themePath_; QtSettingsProvider* settings_; diff --git a/Swift/QtUI/QtDBUSURIHandler.cpp b/Swift/QtUI/QtDBUSURIHandler.cpp new file mode 100644 index 0000000..9b69ca6 --- /dev/null +++ b/Swift/QtUI/QtDBUSURIHandler.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "QtDBUSURIHandler.h" + +#include <QDBusAbstractAdaptor> +#include <QDBusConnection> + +#include "QtSwiftUtil.h" + +using namespace Swift; + +namespace { + class DBUSAdaptor: public QDBusAbstractAdaptor { + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "im.swift.Swift.URIHandler"); + public: + DBUSAdaptor(QtDBUSURIHandler* uriHandler) : QDBusAbstractAdaptor(uriHandler), uriHandler(uriHandler) { + } + + public slots: + void openURI(const QString& uri) { + uriHandler->onURI(Q2PSTRING(uri)); + } + + private: + QtDBUSURIHandler* uriHandler; + }; +} + +QtDBUSURIHandler::QtDBUSURIHandler() { + new DBUSAdaptor(this); + QDBusConnection connection = QDBusConnection::sessionBus(); + connection.registerService("im.swift.Swift.URIHandler"); + connection.registerObject("/", this); +} + +#include "QtDBUSURIHandler.moc" diff --git a/Swift/QtUI/QtDBUSURIHandler.h b/Swift/QtUI/QtDBUSURIHandler.h new file mode 100644 index 0000000..be1872e --- /dev/null +++ b/Swift/QtUI/QtDBUSURIHandler.h @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QObject> +#include <SwifTools/URIHandler/URIHandler.h> + +namespace Swift { + class QtDBUSURIHandler : public QObject, public URIHandler { + public: + QtDBUSURIHandler(); + }; +} diff --git a/Swift/QtUI/QtFormWidget.cpp b/Swift/QtUI/QtFormWidget.cpp new file mode 100644 index 0000000..050ff27 --- /dev/null +++ b/Swift/QtUI/QtFormWidget.cpp @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2010-2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <Swift/QtUI/QtFormWidget.h> + +#include <QGridLayout> +#include <QLabel> +#include <QListWidget> +#include <QLineEdit> +#include <QTextEdit> +#include <QCheckBox> +#include <QScrollArea> + +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swiften/Base/foreach.h> + +namespace Swift { + +QtFormWidget::QtFormWidget(Form::ref form, QWidget* parent) : QWidget(parent), form_(form) { + QGridLayout* thisLayout = new QGridLayout(this); + int row = 0; + if (!form->getTitle().empty()) { + QLabel* instructions = new QLabel(("<b>" + form->getTitle() + "</b>").c_str(), this); + thisLayout->addWidget(instructions, row++, 0, 1, 2); + } + if (!form->getInstructions().empty()) { + QLabel* instructions = new QLabel(form->getInstructions().c_str(), this); + thisLayout->addWidget(instructions, row++, 0, 1, 2); + } + QScrollArea* scrollArea = new QScrollArea(this); + thisLayout->addWidget(scrollArea); + QWidget* scroll = new QWidget(this); + QGridLayout* layout = new QGridLayout(scroll); + foreach (boost::shared_ptr<FormField> field, form->getFields()) { + QWidget* widget = createWidget(field); + if (widget) { + layout->addWidget(new QLabel(field->getLabel().c_str(), this), row, 0); + layout->addWidget(widget, row++, 1); + } + } + scrollArea->setWidget(scroll); + scrollArea->setWidgetResizable(true); +} + +QtFormWidget::~QtFormWidget() { + +} + +QListWidget* QtFormWidget::createList(FormField::ref field) { + QListWidget* listWidget = new QListWidget(this); + listWidget->setSortingEnabled(false); + listWidget->setSelectionMode(boost::dynamic_pointer_cast<ListMultiFormField>(field) ? QAbstractItemView::MultiSelection : QAbstractItemView::SingleSelection); + foreach (FormField::Option option, field->getOptions()) { + listWidget->addItem(option.label.c_str()); + } + boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field); + boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field); + for (int i = 0; i < listWidget->count(); i++) { + QListWidgetItem* item = listWidget->item(i); + bool selected = false; + if (listSingleField) { + selected = (item->text() == QString(listSingleField->getValue().c_str())); + } + else if (listMultiField) { + std::string text = Q2PSTRING(item->text()); + selected = (std::find(listMultiField->getValue().begin(), listMultiField->getValue().end(), text) != listMultiField->getValue().end()); + } + item->setSelected(selected); + } + return listWidget; +} + +QWidget* QtFormWidget::createWidget(FormField::ref field) { + QWidget* widget = NULL; + boost::shared_ptr<BooleanFormField> booleanField = boost::dynamic_pointer_cast<BooleanFormField>(field); + if (booleanField) { + QCheckBox* checkWidget = new QCheckBox(this); + checkWidget->setCheckState(booleanField->getValue() ? Qt::Checked : Qt::Unchecked); + widget = checkWidget; + } + boost::shared_ptr<FixedFormField> fixedField = boost::dynamic_pointer_cast<FixedFormField>(field); + if (fixedField) { + QString value = fixedField->getValue().c_str(); + widget = new QLabel(value, this); + } + boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field); + if (listSingleField) { + widget = createList(field); + } + boost::shared_ptr<TextMultiFormField> textMultiField = boost::dynamic_pointer_cast<TextMultiFormField>(field); + if (textMultiField) { + QString value = textMultiField->getValue().c_str(); + widget = new QTextEdit(value, this); + } + boost::shared_ptr<TextPrivateFormField> textPrivateField = boost::dynamic_pointer_cast<TextPrivateFormField>(field); + if (textPrivateField) { + QString value = textPrivateField->getValue().c_str(); + QLineEdit* lineWidget = new QLineEdit(value, this); + lineWidget->setEchoMode(QLineEdit::Password); + widget = lineWidget; + } + boost::shared_ptr<TextSingleFormField> textSingleField = boost::dynamic_pointer_cast<TextSingleFormField>(field); + if (textSingleField) { + QString value = textSingleField->getValue().c_str(); + widget = new QLineEdit(value, this); + } + boost::shared_ptr<JIDSingleFormField> jidSingleField = boost::dynamic_pointer_cast<JIDSingleFormField>(field); + if (jidSingleField) { + QString value = jidSingleField->getValue().toString().c_str(); + widget = new QLineEdit(value, this); + } + boost::shared_ptr<JIDMultiFormField> jidMultiField = boost::dynamic_pointer_cast<JIDMultiFormField>(field); + if (jidMultiField) { + QString text; + bool prev = false; + foreach (JID line, jidMultiField->getValue()) { + if (prev) { + text += "\n"; + } + prev = true; + text += line.toString().c_str(); + } + widget = new QTextEdit(text, this); + } + boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field); + if (listMultiField) { + widget = createList(field); + } + boost::shared_ptr<HiddenFormField> hiddenField = boost::dynamic_pointer_cast<HiddenFormField>(field); + if (hiddenField) { + } + fields_[field->getName()] = widget; + return widget; +} + +Form::ref QtFormWidget::getCompletedForm() { + Form::ref result(new Form(Form::SubmitType)); + foreach (boost::shared_ptr<FormField> field, form_->getFields()) { + boost::shared_ptr<FormField> resultField; + boost::shared_ptr<BooleanFormField> booleanField = boost::dynamic_pointer_cast<BooleanFormField>(field); + if (booleanField) { + resultField = FormField::ref(BooleanFormField::create(qobject_cast<QCheckBox*>(fields_[field->getName()])->checkState() == Qt::Checked)); + } + boost::shared_ptr<FixedFormField> fixedField = boost::dynamic_pointer_cast<FixedFormField>(field); + if (fixedField) { + resultField = FormField::ref(FixedFormField::create(fixedField->getValue())); + } + boost::shared_ptr<ListSingleFormField> listSingleField = boost::dynamic_pointer_cast<ListSingleFormField>(field); + if (listSingleField) { + QListWidget* listWidget = qobject_cast<QListWidget*>(fields_[field->getName()]); + if (listWidget->selectedItems().size() > 0) { + int i = listWidget->row(listWidget->selectedItems()[0]); + resultField = FormField::ref(ListSingleFormField::create(field->getOptions()[i].value)); + } + else { + resultField = FormField::ref(ListSingleFormField::create()); + } + } + boost::shared_ptr<TextMultiFormField> textMultiField = boost::dynamic_pointer_cast<TextMultiFormField>(field); + if (textMultiField) { + QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]); + QString string = widget->toPlainText(); + if (string.isEmpty()) { + resultField = FormField::ref(TextMultiFormField::create()); + } + else { + resultField = FormField::ref(TextMultiFormField::create(Q2PSTRING(string))); + } + } + boost::shared_ptr<TextPrivateFormField> textPrivateField = boost::dynamic_pointer_cast<TextPrivateFormField>(field); + if (textPrivateField) { + QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]); + QString string = widget->text(); + if (string.isEmpty()) { + resultField = FormField::ref(TextPrivateFormField::create()); + } + else { + resultField = FormField::ref(TextPrivateFormField::create(Q2PSTRING(string))); + } + } + boost::shared_ptr<TextSingleFormField> textSingleField = boost::dynamic_pointer_cast<TextSingleFormField>(field); + if (textSingleField) { + QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]); + QString string = widget->text(); + if (string.isEmpty()) { + resultField = FormField::ref(TextSingleFormField::create()); + } + else { + resultField = FormField::ref(TextSingleFormField::create(Q2PSTRING(string))); + } + } + boost::shared_ptr<JIDSingleFormField> jidSingleField = boost::dynamic_pointer_cast<JIDSingleFormField>(field); + if (jidSingleField) { + QLineEdit* widget = qobject_cast<QLineEdit*>(fields_[field->getName()]); + QString string = widget->text(); + JID jid(Q2PSTRING(string)); + if (string.isEmpty()) { + resultField = FormField::ref(JIDSingleFormField::create()); + } + else { + resultField = FormField::ref(JIDSingleFormField::create(jid)); + } + } + boost::shared_ptr<JIDMultiFormField> jidMultiField = boost::dynamic_pointer_cast<JIDMultiFormField>(field); + if (jidMultiField) { + QTextEdit* widget = qobject_cast<QTextEdit*>(fields_[field->getName()]); + QString string = widget->toPlainText(); + if (string.isEmpty()) { + resultField = FormField::ref(JIDMultiFormField::create()); + } + else { + QStringList lines = string.split("\n"); + std::vector<JID> value; + foreach (QString line, lines) { + value.push_back(JID(Q2PSTRING(line))); + } + resultField = FormField::ref(JIDMultiFormField::create(value)); + } + } + boost::shared_ptr<ListMultiFormField> listMultiField = boost::dynamic_pointer_cast<ListMultiFormField>(field); + if (listMultiField) { + QListWidget* listWidget = qobject_cast<QListWidget*>(fields_[field->getName()]); + std::vector<std::string> values; + foreach (QListWidgetItem* item, listWidget->selectedItems()) { + values.push_back(field->getOptions()[listWidget->row(item)].value); + } + resultField = FormField::ref(ListMultiFormField::create(values)); + } + boost::shared_ptr<HiddenFormField> hiddenField = boost::dynamic_pointer_cast<HiddenFormField>(field); + if (hiddenField) { + resultField = FormField::ref(HiddenFormField::create(hiddenField->getValue())); + } + resultField->setName(field->getName()); + result->addField(resultField); + } + return result; +} + +} diff --git a/Swift/QtUI/QtFormWidget.h b/Swift/QtUI/QtFormWidget.h new file mode 100644 index 0000000..2fb7b98 --- /dev/null +++ b/Swift/QtUI/QtFormWidget.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2011 Kevin Smith + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QWidget> + +#include <map> +#include <Swiften/Elements/Form.h> + +class QListWidget; + +namespace Swift { + +class QtFormWidget : public QWidget { + Q_OBJECT + public: + QtFormWidget(Form::ref form, QWidget* parent = NULL); + virtual ~QtFormWidget(); + Form::ref getCompletedForm(); + private: + QWidget* createWidget(FormField::ref field); + QListWidget* createList(FormField::ref field); + std::map<std::string, QWidget*> fields_; + Form::ref form_; +}; + +} diff --git a/Swift/QtUI/QtLoginWindow.cpp b/Swift/QtUI/QtLoginWindow.cpp index d0ab61e..2514259 100644 --- a/Swift/QtUI/QtLoginWindow.cpp +++ b/Swift/QtUI/QtLoginWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -39,7 +39,7 @@ namespace Swift{ -QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() { +QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow(), forgetful_(false) { uiEventStream_ = uiEventStream; setWindowTitle("Swift"); @@ -56,9 +56,9 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() { stack_ = new QStackedWidget(centralWidget); topLayout->addWidget(stack_); topLayout->setMargin(0); - QWidget *wrapperWidget = new QWidget(this); - wrapperWidget->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); - QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, wrapperWidget); + loginWidgetWrapper_ = new QWidget(this); + loginWidgetWrapper_->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + QBoxLayout *layout = new QBoxLayout(QBoxLayout::TopToBottom, loginWidgetWrapper_); layout->addStretch(2); QLabel* logo = new QLabel(this); @@ -139,7 +139,7 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() { layout->addWidget(loginAutomatically_); connect(loginButton_, SIGNAL(clicked()), SLOT(loginClicked())); - stack_->addWidget(wrapperWidget); + stack_->addWidget(loginWidgetWrapper_); #ifdef SWIFTEN_PLATFORM_MACOSX menuBar_ = new QMenuBar(NULL); #else @@ -162,9 +162,9 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() { connect(aboutAction, SIGNAL(triggered()), SLOT(handleAbout())); swiftMenu_->addAction(aboutAction); - QAction* xmlConsoleAction = new QAction(tr("&Show Debug Console"), this); - connect(xmlConsoleAction, SIGNAL(triggered()), SLOT(handleShowXMLConsole())); - generalMenu_->addAction(xmlConsoleAction); + xmlConsoleAction_ = new QAction(tr("&Show Debug Console"), this); + connect(xmlConsoleAction_, SIGNAL(triggered()), SLOT(handleShowXMLConsole())); + generalMenu_->addAction(xmlConsoleAction_); toggleSoundsAction_ = new QAction(tr("&Play Sounds"), this); toggleSoundsAction_->setCheckable(true); @@ -197,6 +197,17 @@ QtLoginWindow::QtLoginWindow(UIEventStream* uiEventStream) : QMainWindow() { this->show(); } +void QtLoginWindow::setRememberingAllowed(bool allowed) { + forgetful_ = true; + remember_->setEnabled(allowed); + loginAutomatically_->setEnabled(allowed); + xmlConsoleAction_->setEnabled(allowed); + if (!allowed) { + remember_->setChecked(false); + loginAutomatically_->setChecked(false); + } +} + bool QtLoginWindow::eventFilter(QObject *obj, QEvent *event) { if (obj == username_->view() && event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); @@ -284,11 +295,9 @@ void QtLoginWindow::handleUsernameTextChanged() { } void QtLoginWindow::loggedOut() { - if (stack_->count() > 1) { - QWidget* current = stack_->currentWidget(); - stack_->setCurrentIndex(0); - stack_->removeWidget(current); - } + stack_->removeWidget(stack_->currentWidget()); + stack_->addWidget(loginWidgetWrapper_); + stack_->setCurrentWidget(loginWidgetWrapper_); setInitialMenus(); setIsLoggingIn(false); } @@ -306,6 +315,10 @@ void QtLoginWindow::setIsLoggingIn(bool loggingIn) { void QtLoginWindow::loginClicked() { if (username_->isEnabled()) { onLoginRequest(Q2PSTRING(username_->currentText()), Q2PSTRING(password_->text()), Q2PSTRING(certificateFile_), remember_->isChecked(), loginAutomatically_->isChecked()); + if (forgetful_) { /* Mustn't remember logins */ + username_->clearEditText(); + password_->setText(""); + } } else { onCancelLoginRequest(); } @@ -370,6 +383,7 @@ void QtLoginWindow::setInitialMenus() { void QtLoginWindow::morphInto(MainWindow *mainWindow) { QtMainWindow *qtMainWindow = dynamic_cast<QtMainWindow*>(mainWindow); assert(qtMainWindow); + stack_->removeWidget(loginWidgetWrapper_); stack_->addWidget(qtMainWindow); stack_->setCurrentWidget(qtMainWindow); setEnabled(true); diff --git a/Swift/QtUI/QtLoginWindow.h b/Swift/QtUI/QtLoginWindow.h index 3f3b5f8..4628af7 100644 --- a/Swift/QtUI/QtLoginWindow.h +++ b/Swift/QtUI/QtLoginWindow.h @@ -1,11 +1,10 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ -#ifndef SWIFT_QtLoginWindow_H -#define SWIFT_QtLoginWindow_H +#pragma once #include <QMainWindow> #include <QPointer> @@ -37,6 +36,7 @@ namespace Swift { virtual void removeAvailableAccount(const std::string& jid); virtual void setLoginAutomatically(bool loginAutomatically); virtual void setIsLoggingIn(bool loggingIn); + virtual void setRememberingAllowed(bool allowed); void selectUser(const std::string& user); bool askUserToTrustCertificatePermanently(const std::string& message, Certificate::ref certificate); void hide(); @@ -65,6 +65,7 @@ namespace Swift { private: void setInitialMenus(); + QWidget* loginWidgetWrapper_; QStringList usernames_; QStringList passwords_; QStringList certificateFiles_; @@ -85,7 +86,7 @@ namespace Swift { QAction* toggleNotificationsAction_; UIEventStream* uiEventStream_; QPointer<QtAboutWidget> aboutDialog_; + bool forgetful_; + QAction* xmlConsoleAction_; }; } - -#endif diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp index 6391961..51aaf3e 100644 --- a/Swift/QtUI/QtMainWindow.cpp +++ b/Swift/QtUI/QtMainWindow.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -21,20 +21,25 @@ #include <QAction> #include <QTabWidget> -#include "QtSwiftUtil.h" -#include "QtTabWidget.h" -#include "Roster/QtTreeWidget.h" -#include "Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h" -#include "Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h" -#include "Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h" -#include "Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h" -#include "Swift/Controllers/UIEvents/JoinMUCUIEvent.h" -#include "Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h" +#include <Swift/QtUI/QtSwiftUtil.h> +#include <Swift/QtUI/QtTabWidget.h> +#include <Swift/QtUI/QtSettingsProvider.h> +#include <Roster/QtTreeWidget.h> +#include <Swift/Controllers/UIEvents/RequestJoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestAddUserDialogUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestChatWithUserDialogUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestProfileEditorUIEvent.h> +#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h> +#include <Swift/Controllers/UIEvents/ToggleShowOfflineUIEvent.h> +#include <Swift/Controllers/UIEvents/RequestAdHocUIEvent.h> namespace Swift { +#define CURRENT_ROSTER_TAB "current_roster_tab" + QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventStream) : QWidget(), MainWindow(false) { uiEventStream_ = uiEventStream; + settings_ = settings; setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); QBoxLayout *mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this); mainLayout->setContentsMargins(0,0,0,0); @@ -67,9 +72,14 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS connect(eventWindow_, SIGNAL(onNewEventCountUpdated(int)), this, SLOT(handleEventCountUpdated(int))); chatListWindow_ = new QtChatListWindow(uiEventStream_); + connect(chatListWindow_, SIGNAL(onCountUpdated(int)), this, SLOT(handleChatCountUpdated(int))); - tabs_->addTab(eventWindow_, tr("&Notices")); tabs_->addTab(chatListWindow_, tr("C&hats")); + tabs_->addTab(eventWindow_, tr("&Notices")); + + tabs_->setCurrentIndex(settings_->getIntSetting(CURRENT_ROSTER_TAB, 0)); + + connect(tabs_, SIGNAL(currentChanged(int)), this, SLOT(handleTabChanged(int))); this->setLayout(mainLayout); @@ -99,6 +109,8 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS chatUserAction_ = new QAction(tr("Start &Chat"), this); connect(chatUserAction_, SIGNAL(triggered(bool)), this, SLOT(handleChatUserActionTriggered(bool))); actionsMenu->addAction(chatUserAction_); + serverAdHocMenu_ = new QMenu(tr("Run Server Command"), this); + actionsMenu->addMenu(serverAdHocMenu_); actionsMenu->addSeparator(); QAction* signOutAction = new QAction(tr("&Sign Out"), this); connect(signOutAction, SIGNAL(triggered()), SLOT(handleSignOutAction())); @@ -106,6 +118,12 @@ QtMainWindow::QtMainWindow(QtSettingsProvider* settings, UIEventStream* uiEventS connect(treeWidget_, SIGNAL(onSomethingSelectedChanged(bool)), editUserAction_, SLOT(setEnabled(bool))); + setAvailableAdHocCommands(std::vector<DiscoItems::Item>()); + QAction* adHocAction = new QAction(tr("Collecting commands..."), this); + adHocAction->setEnabled(false); + serverAdHocMenu_->addAction(adHocAction); + serverAdHocCommandActions_.append(adHocAction); + lastOfflineState_ = false; uiEventStream_->onUIEvent.connect(boost::bind(&QtMainWindow::handleUIEvent, this, _1)); } @@ -114,6 +132,10 @@ QtMainWindow::~QtMainWindow() { uiEventStream_->onUIEvent.disconnect(boost::bind(&QtMainWindow::handleUIEvent, this, _1)); } +void QtMainWindow::handleTabChanged(int index) { + settings_->storeInt(CURRENT_ROSTER_TAB, index); +} + QtEventWindow* QtMainWindow::getEventWindow() { return eventWindow_; } @@ -132,7 +154,7 @@ void QtMainWindow::handleEditProfileRequest() { void QtMainWindow::handleEventCountUpdated(int count) { QColor eventTabColor = (count == 0) ? QColor() : QColor(255, 0, 0); // invalid resets to default - int eventIndex = 1; + int eventIndex = 2; tabs_->tabBar()->setTabTextColor(eventIndex, eventTabColor); QString text = tr("&Notices"); if (count > 0) { @@ -141,6 +163,17 @@ void QtMainWindow::handleEventCountUpdated(int count) { tabs_->setTabText(eventIndex, text); } +void QtMainWindow::handleChatCountUpdated(int count) { + QColor chatTabColor = (count == 0) ? QColor() : QColor(255, 0, 0); // invalid resets to default + int chatIndex = 1; + tabs_->tabBar()->setTabTextColor(chatIndex, chatTabColor); + QString text = tr("&Chats"); + if (count > 0) { + text += QString(" (%1)").arg(count); + } + tabs_->setTabText(chatIndex, text); +} + void QtMainWindow::handleAddUserActionTriggered(bool /*checked*/) { boost::shared_ptr<UIEvent> event(new RequestAddUserDialogUIEvent()); uiEventStream_->send(event); @@ -206,6 +239,33 @@ void QtMainWindow::setConnecting() { meView_->setConnecting(); } +void QtMainWindow::handleAdHocActionTriggered(bool /*checked*/) { + QAction* action = qobject_cast<QAction*>(sender()); + assert(action); + DiscoItems::Item command = serverAdHocCommands_[serverAdHocCommandActions_.indexOf(action)]; + uiEventStream_->send(boost::shared_ptr<UIEvent>(new RequestAdHocUIEvent(command))); +} + +void QtMainWindow::setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands) { + serverAdHocCommands_ = commands; + foreach (QAction* action, serverAdHocCommandActions_) { + delete action; + } + serverAdHocMenu_->clear(); + serverAdHocCommandActions_.clear(); + foreach (DiscoItems::Item command, commands) { + QAction* action = new QAction(P2QSTRING(command.getName()), this); + connect(action, SIGNAL(triggered(bool)), this, SLOT(handleAdHocActionTriggered(bool))); + serverAdHocMenu_->addAction(action); + serverAdHocCommandActions_.append(action); + } + if (serverAdHocCommandActions_.isEmpty()) { + QAction* action = new QAction(tr("No Available Commands"), this); + action->setEnabled(false); + serverAdHocMenu_->addAction(action); + serverAdHocCommandActions_.append(action); + } +} } diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h index 3462bb0..bb3e9df 100644 --- a/Swift/QtUI/QtMainWindow.h +++ b/Swift/QtUI/QtMainWindow.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -8,6 +8,7 @@ #include <QWidget> #include <QMenu> +#include <QList> #include "Swift/Controllers/UIInterfaces/MainWindow.h" #include "Swift/QtUI/QtRosterHeader.h" #include "Swift/QtUI/EventViewer/QtEventWindow.h" @@ -20,7 +21,7 @@ class QLineEdit; class QPushButton; class QToolBar; class QAction; - +class QMenu; class QTabWidget; namespace Swift { @@ -46,6 +47,7 @@ namespace Swift { QtEventWindow* getEventWindow(); QtChatListWindow* getChatListWindow(); void setRosterModel(Roster* roster); + void setAvailableAdHocCommands(const std::vector<DiscoItems::Item>& commands); private slots: void handleStatusChanged(StatusShow::Type showType, const QString &statusMessage); void handleUIEvent(boost::shared_ptr<UIEvent> event); @@ -55,10 +57,14 @@ namespace Swift { void handleEditProfileAction(); void handleAddUserActionTriggered(bool checked); void handleChatUserActionTriggered(bool checked); + void handleAdHocActionTriggered(bool checked); void handleEventCountUpdated(int count); + void handleChatCountUpdated(int count); void handleEditProfileRequest(); + void handleTabChanged(int index); private: + QtSettingsProvider* settings_; std::vector<QMenu*> menus_; QtTreeWidget* treeWidget_; QtRosterHeader* meView_; @@ -66,6 +72,7 @@ namespace Swift { QAction* editUserAction_; QAction* chatUserAction_; QAction* showOfflineAction_; + QMenu* serverAdHocMenu_; QtTabWidget* tabs_; QWidget* contactsTabWidget_; QWidget* eventsTabWidget_; @@ -73,5 +80,7 @@ namespace Swift { QtChatListWindow* chatListWindow_; UIEventStream* uiEventStream_; bool lastOfflineState_; + std::vector<DiscoItems::Item> serverAdHocCommands_; + QList<QAction*> serverAdHocCommandActions_; }; } diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp index 7830150..1d820b1 100644 --- a/Swift/QtUI/QtSwift.cpp +++ b/Swift/QtUI/QtSwift.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010 Kevin Smith + * Copyright (c) 2010-2011 Kevin Smith * Licensed under the GNU General Public License v3. * See Documentation/Licenses/GPLv3.txt for more information. */ @@ -18,13 +18,11 @@ #include "QtUIFactory.h" #include "QtChatWindowFactory.h" #include <Swiften/Base/Log.h> -#include <Swift/Controllers/CertificateFileStorageFactory.h> +#include <Swift/Controllers/Storages/CertificateFileStorageFactory.h> +#include "Swift/Controllers/Storages/FileStoragesFactory.h" #include "SwifTools/Application/PlatformApplicationPathProvider.h" -#include "Swiften/Avatars/AvatarFileStorage.h" -#include "Swiften/Disco/CapsFileStorage.h" #include <string> #include "Swiften/Base/Platform.h" -#include "Swift/Controllers/FileStoragesFactory.h" #include "Swiften/Elements/Presence.h" #include "Swiften/Client/Client.h" #include "Swift/Controllers/MainController.h" @@ -32,20 +30,30 @@ #include "Swift/Controllers/BuildVersion.h" #include "SwifTools/AutoUpdater/AutoUpdater.h" #include "SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h" + #if defined(SWIFTEN_PLATFORM_WINDOWS) #include "WindowsNotifier.h" -#endif -#if defined(HAVE_GROWL) +#elif defined(HAVE_GROWL) #include "SwifTools/Notifier/GrowlNotifier.h" #elif defined(SWIFTEN_PLATFORM_LINUX) #include "FreeDesktopNotifier.h" #else #include "SwifTools/Notifier/NullNotifier.h" #endif + #if defined(SWIFTEN_PLATFORM_MACOSX) #include "SwifTools/Dock/MacOSXDock.h" -#endif +#else #include "SwifTools/Dock/NullDock.h" +#endif + +#if defined(SWIFTEN_PLATFORM_MACOSX) +#include "QtURIHandler.h" +#elif defined(SWIFTEN_PLATFORM_WIN32) +#include <SwifTools/URIHandler/NullURIHandler.h> +#else +#include "QtDBUSURIHandler.h" +#endif namespace Swift{ @@ -66,6 +74,7 @@ po::options_description QtSwift::getOptionsDescription() { ("latency-debug", "Use latency debugging (unsupported)") ("multi-account", po::value<int>()->default_value(1), "Number of accounts to open windows for (unsupported)") ("start-minimized", "Don't show the login/roster window at startup") + ("eagle-mode", "Settings more suitable for military/secure deployments") ; return result; } @@ -96,6 +105,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa tabs_ = options.count("no-tabs") && !(splitter_ > 0) ? NULL : new QtChatTabs(); bool startMinimized = options.count("start-minimized") > 0; + bool eagleMode = options.count("eagle-mode") > 0; settings_ = new QtSettingsProvider(); applicationPathProvider_ = new PlatformApplicationPathProvider(SWIFT_APPLICATION_NAME); storagesFactory_ = new FileStoragesFactory(applicationPathProvider_->getDataDir()); @@ -124,6 +134,14 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa dock_ = new NullDock(); #endif +#if defined(SWIFTEN_PLATFORM_MACOSX) + uriHandler_ = new QtURIHandler(); +#elif defined(SWIFTEN_PLATFORM_WIN32) + uriHandler_ = new NullURIHandler(); +#else + uriHandler_ = new QtDBUSURIHandler(); +#endif + if (splitter_) { splitter_->show(); } @@ -146,7 +164,9 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa certificateStorageFactory_, dock_, notifier_, - options.count("latency-debug") > 0); + uriHandler_, + options.count("latency-debug") > 0, + eagleMode); mainControllers_.push_back(mainController); } @@ -173,6 +193,7 @@ QtSwift::~QtSwift() { } delete tabs_; delete splitter_; + delete uriHandler_; delete dock_; delete soundPlayer_; delete chatWindowFactory_; diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h index 978fa14..4bf5c97 100644 --- a/Swift/QtUI/QtSwift.h +++ b/Swift/QtUI/QtSwift.h @@ -44,6 +44,7 @@ namespace Swift { class QtMUCSearchWindowFactory; class QtUserSearchWindowFactory; class EventLoop; + class URIHandler; class QtSwift : public QObject { Q_OBJECT @@ -63,6 +64,7 @@ namespace Swift { QSplitter* splitter_; QtSoundPlayer* soundPlayer_; Dock* dock_; + URIHandler* uriHandler_; QtChatTabs* tabs_; ApplicationPathProvider* applicationPathProvider_; StoragesFactory* storagesFactory_; diff --git a/Swift/QtUI/QtTextEdit.cpp b/Swift/QtUI/QtTextEdit.cpp index 3668220..3a62325 100644 --- a/Swift/QtUI/QtTextEdit.cpp +++ b/Swift/QtUI/QtTextEdit.cpp @@ -4,7 +4,7 @@ * See Documentation/Licenses/GPLv3.txt for more information. */ -#include "QtTextEdit.h" +#include <Swift/QtUI/QtTextEdit.h> #include <QFontMetrics> #include <QKeyEvent> @@ -22,19 +22,25 @@ void QtTextEdit::keyPressEvent(QKeyEvent* event) { if ((key == Qt::Key_Enter || key == Qt::Key_Return) && (modifiers == Qt::NoModifier || modifiers == Qt::KeypadModifier)) { emit returnPressed(); - } else if (((key == Qt::Key_PageUp || key == Qt::Key_PageDown) && modifiers == Qt::ShiftModifier) + } + else if (((key == Qt::Key_PageUp || key == Qt::Key_PageDown) && modifiers == Qt::ShiftModifier) || (key == Qt::Key_C && modifiers == Qt::ControlModifier && textCursor().selectedText().isEmpty()) || (key == Qt::Key_W && modifiers == Qt::ControlModifier) || (key == Qt::Key_PageUp && modifiers == Qt::ControlModifier) || (key == Qt::Key_PageDown && modifiers == Qt::ControlModifier) -// || (key == Qt::Key_Left && modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) -// || (key == Qt::Key_Right && modifiers == (Qt::ControlModifier | Qt::ShiftModifier)) || (key == Qt::Key_Tab && modifiers == Qt::ControlModifier) || (key == Qt::Key_A && modifiers == Qt::AltModifier) || (key == Qt::Key_Tab) ) { emit unhandledKeyPressEvent(event); - } else { + } + else if ((key == Qt::Key_Up) + || (key == Qt::Key_Down) + ){ + emit unhandledKeyPressEvent(event); + QTextEdit::keyPressEvent(event); + } + else { QTextEdit::keyPressEvent(event); } } diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp index 35fbfcd..40ce95e 100644 --- a/Swift/QtUI/QtUIFactory.cpp +++ b/Swift/QtUI/QtUIFactory.cpp @@ -23,10 +23,14 @@ #include "UserSearch/QtUserSearchWindow.h" #include "QtProfileWindow.h" #include "QtContactEditWindow.h" +#include "QtAdHocCommandWindow.h" + +#define CHATWINDOW_FONT_SIZE "chatWindowFontSize" namespace Swift { QtUIFactory::QtUIFactory(QtSettingsProvider* settings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, bool startMinimized) : settings(settings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), lastMainWindow(NULL), loginWindow(NULL), startMinimized(startMinimized) { + chatFontSize = settings->getIntSetting(CHATWINDOW_FONT_SIZE, 0); } XMLConsoleWidget* QtUIFactory::createXMLConsoleWidget() { @@ -80,7 +84,28 @@ MUCSearchWindow* QtUIFactory::createMUCSearchWindow() { } ChatWindow* QtUIFactory::createChatWindow(const JID& contact, UIEventStream* eventStream) { - return chatWindowFactory->createChatWindow(contact, eventStream); + QtChatWindow* window = dynamic_cast<QtChatWindow*>(chatWindowFactory->createChatWindow(contact, eventStream)); + chatWindows.push_back(window); + std::vector<QPointer<QtChatWindow> > deletions; + foreach (QPointer<QtChatWindow> existingWindow, chatWindows) { + if (existingWindow.isNull()) { + deletions.push_back(existingWindow); + } else { + connect(window, SIGNAL(fontResized(int)), existingWindow, SLOT(handleFontResized(int))); + connect(existingWindow, SIGNAL(fontResized(int)), window, SLOT(handleFontResized(int))); + } + } + foreach (QPointer<QtChatWindow> deletedWindow, deletions) { + chatWindows.erase(std::remove(chatWindows.begin(), chatWindows.end(), deletedWindow), chatWindows.end()); + } + connect(window, SIGNAL(fontResized(int)), this, SLOT(handleChatWindowFontResized(int))); + window->handleFontResized(chatFontSize); + return window; +} + +void QtUIFactory::handleChatWindowFontResized(int size) { + chatFontSize = size; + settings->storeInt(CHATWINDOW_FONT_SIZE, size); } UserSearchWindow* QtUIFactory::createUserSearchWindow(UserSearchWindow::Type type, UIEventStream* eventStream, const std::set<std::string>& groups) { @@ -99,5 +124,8 @@ ContactEditWindow* QtUIFactory::createContactEditWindow() { return new QtContactEditWindow(); } +void QtUIFactory::createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command) { + new QtAdHocCommandWindow(command); +} } diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h index ddaaf6e..a576ded 100644 --- a/Swift/QtUI/QtUIFactory.h +++ b/Swift/QtUI/QtUIFactory.h @@ -7,6 +7,7 @@ #pragma once #include <QObject> +#include <QPointer> #include <Swift/Controllers/UIInterfaces/UIFactory.h> @@ -20,6 +21,7 @@ namespace Swift { class QtMainWindow; class QtChatTheme; class QtChatWindowFactory; + class QtChatWindow; class QtUIFactory : public QObject, public UIFactory { Q_OBJECT @@ -37,9 +39,11 @@ namespace Swift { virtual JoinMUCWindow* createJoinMUCWindow(); virtual ProfileWindow* createProfileWindow(); virtual ContactEditWindow* createContactEditWindow(); + virtual void createAdHocCommandWindow(boost::shared_ptr<OutgoingAdHocCommandSession> command); private slots: void handleLoginWindowGeometryChanged(); + void handleChatWindowFontResized(int); private: QtSettingsProvider* settings; @@ -49,6 +53,8 @@ namespace Swift { QtChatWindowFactory* chatWindowFactory; QtMainWindow* lastMainWindow; QtLoginWindow* loginWindow; + std::vector<QPointer<QtChatWindow> > chatWindows; bool startMinimized; + int chatFontSize; }; } diff --git a/Swift/QtUI/QtURIHandler.cpp b/Swift/QtUI/QtURIHandler.cpp new file mode 100644 index 0000000..43f3ed1 --- /dev/null +++ b/Swift/QtUI/QtURIHandler.cpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include "QtURIHandler.h" + +#include <QCoreApplication> +#include <QFileOpenEvent> +#include <QUrl> + +#include "QtSwiftUtil.h" +#ifdef Q_WS_MAC +#include <SwifTools/URIHandler/MacOSXURIHandlerHelpers.h> +#endif + +using namespace Swift; + +QtURIHandler::QtURIHandler() { + qApp->installEventFilter(this); +#ifdef Q_WS_MAC + registerAppAsDefaultXMPPURIHandler(); +#endif +} + +bool QtURIHandler::eventFilter(QObject*, QEvent* event) { + if (event->type() == QEvent::FileOpen) { + QFileOpenEvent* fileOpenEvent = static_cast<QFileOpenEvent*>(event); + if (fileOpenEvent->url().scheme() == "xmpp") { + onURI(Q2PSTRING(fileOpenEvent->url().toString())); + return true; + } + } + return false; +} diff --git a/Swift/QtUI/QtURIHandler.h b/Swift/QtUI/QtURIHandler.h new file mode 100644 index 0000000..a02114a --- /dev/null +++ b/Swift/QtUI/QtURIHandler.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#pragma once + +#include <QObject> +#include <SwifTools/URIHandler/URIHandler.h> + +class QUrl; + +namespace Swift { + class QtURIHandler : public QObject, public URIHandler { + public: + QtURIHandler(); + + private: + bool eventFilter(QObject* obj, QEvent* event); + }; +} diff --git a/Swift/QtUI/QtWebView.cpp b/Swift/QtUI/QtWebView.cpp index 9d12010..5d25071 100644 --- a/Swift/QtUI/QtWebView.cpp +++ b/Swift/QtUI/QtWebView.cpp @@ -59,6 +59,8 @@ void QtWebView::contextMenuEvent(QContextMenuEvent* ev) { // Add our own custom actions menu->addAction(tr("Clear"), this, SIGNAL(clearRequested())); + menu->addAction(tr("Increase font size"), this, SIGNAL(fontGrowRequested())); + menu->addAction(tr("Decrease font size"), this, SIGNAL(fontShrinkRequested())); menu->exec(ev->globalPos()); delete menu; diff --git a/Swift/QtUI/QtWebView.h b/Swift/QtUI/QtWebView.h index fbd31e3..eb5a82d 100644 --- a/Swift/QtUI/QtWebView.h +++ b/Swift/QtUI/QtWebView.h @@ -22,6 +22,8 @@ namespace Swift { signals: void gotFocus(); void clearRequested(); + void fontGrowRequested(); + void fontShrinkRequested(); protected: void focusInEvent(QFocusEvent* event); diff --git a/Swift/QtUI/QtXMLConsoleWidget.cpp b/Swift/QtUI/QtXMLConsoleWidget.cpp index c1b1d0d..b0c0385 100644 --- a/Swift/QtUI/QtXMLConsoleWidget.cpp +++ b/Swift/QtUI/QtXMLConsoleWidget.cpp @@ -71,12 +71,12 @@ void QtXMLConsoleWidget::closeEvent(QCloseEvent* event) { event->accept(); } -void QtXMLConsoleWidget::handleDataRead(const std::string& data) { - appendTextIfEnabled(std::string(tr("<!-- IN -->").toUtf8()) + "\n" + data + "\n", QColor(33,98,33)); +void QtXMLConsoleWidget::handleDataRead(const SafeByteArray& data) { + appendTextIfEnabled(std::string(tr("<!-- IN -->").toUtf8()) + "\n" + safeByteArrayToString(data) + "\n", QColor(33,98,33)); } -void QtXMLConsoleWidget::handleDataWritten(const std::string& data) { - appendTextIfEnabled(std::string(tr("<!-- OUT -->").toUtf8()) + "\n" + data + "\n", QColor(155,1,0)); +void QtXMLConsoleWidget::handleDataWritten(const SafeByteArray& data) { + appendTextIfEnabled(std::string(tr("<!-- OUT -->").toUtf8()) + "\n" + safeByteArrayToString(data) + "\n", QColor(155,1,0)); } void QtXMLConsoleWidget::appendTextIfEnabled(const std::string& data, const QColor& color) { diff --git a/Swift/QtUI/QtXMLConsoleWidget.h b/Swift/QtUI/QtXMLConsoleWidget.h index a345495..73a3bad 100644 --- a/Swift/QtUI/QtXMLConsoleWidget.h +++ b/Swift/QtUI/QtXMLConsoleWidget.h @@ -23,8 +23,8 @@ namespace Swift { void show(); void activate(); - virtual void handleDataRead(const std::string& data); - virtual void handleDataWritten(const std::string& data); + virtual void handleDataRead(const SafeByteArray& data); + virtual void handleDataWritten(const SafeByteArray& data); private: virtual void closeEvent(QCloseEvent* event); diff --git a/Swift/QtUI/Roster/DelegateCommons.cpp b/Swift/QtUI/Roster/DelegateCommons.cpp index 164b80f..290794d 100644 --- a/Swift/QtUI/Roster/DelegateCommons.cpp +++ b/Swift/QtUI/Roster/DelegateCommons.cpp @@ -6,18 +6,102 @@ #include "DelegateCommons.h" +#include <QtScaledAvatarCache.h> +#include <QFileInfo> + namespace Swift { -void DelegateCommons::drawElidedText(QPainter* painter, const QRect& region, const QString& text) { +void DelegateCommons::drawElidedText(QPainter* painter, const QRect& region, const QString& text, int flags) { QString adjustedText(painter->fontMetrics().elidedText(text, Qt::ElideRight, region.width(), Qt::TextShowMnemonic)); - painter->drawText(region, Qt::AlignTop, adjustedText); + painter->drawText(region, flags, adjustedText); } +void DelegateCommons::paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, int unreadCount) const { + painter->save(); + QRect fullRegion(option.rect); + if ( option.state & QStyle::State_Selected ) { + painter->fillRect(fullRegion, option.palette.highlight()); + painter->setPen(option.palette.highlightedText().color()); + } else { + painter->setPen(QPen(nameColor)); + } + + QRect presenceIconRegion(QPoint(farLeftMargin, fullRegion.top()), QSize(presenceIconWidth, fullRegion.height() - verticalMargin)); + + int calculatedAvatarSize = presenceIconRegion.height(); + //This overlaps the presenceIcon, so must be painted first + QRect avatarRegion(QPoint(presenceIconRegion.right() - presenceIconWidth / 2, presenceIconRegion.top()), QSize(calculatedAvatarSize, calculatedAvatarSize)); + + QPixmap avatarPixmap; + if (!avatarPath.isEmpty()) { + QString scaledAvatarPath = QtScaledAvatarCache(avatarRegion.height()).getScaledAvatarPath(avatarPath); + if (QFileInfo(scaledAvatarPath).exists()) { + avatarPixmap.load(scaledAvatarPath); + } + } + if (avatarPixmap.isNull()) { + avatarPixmap = QPixmap(":/icons/avatar.png").scaled(avatarRegion.height(), avatarRegion.width(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + } + + painter->drawPixmap(avatarRegion.topLeft() + QPoint(((avatarRegion.width() - avatarPixmap.width()) / 2), (avatarRegion.height() - avatarPixmap.height()) / 2), avatarPixmap); + + //Paint the presence icon over the top of the avatar + presenceIcon.paint(painter, presenceIconRegion, Qt::AlignBottom | Qt::AlignHCenter); + + QFontMetrics nameMetrics(nameFont); + painter->setFont(nameFont); + int extraFontWidth = nameMetrics.width("H"); + int leftOffset = avatarRegion.right() + horizontalMargin * 2 + extraFontWidth / 2; + QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0/*-leftOffset*/, 0)); + + int nameHeight = nameMetrics.height() + verticalMargin; + QRect nameRegion(textRegion.adjusted(0, verticalMargin, 0, 0)); + + DelegateCommons::drawElidedText(painter, nameRegion, name); + + + painter->setFont(detailFont); + painter->setPen(QPen(QColor(160,160,160))); + + QRect statusTextRegion(textRegion.adjusted(0, nameHeight, 0, 0)); + DelegateCommons::drawElidedText(painter, statusTextRegion, statusText); + + if (unreadCount > 0) { + QRect unreadRect(fullRegion.right() - unreadCountSize - horizontalMargin, fullRegion.top() + (fullRegion.height() - unreadCountSize) / 2, unreadCountSize, unreadCountSize); + QPen pen(QColor("black")); + pen.setWidth(1); + painter->setRenderHint(QPainter::Antialiasing, true); + painter->setPen(pen); + painter->setBrush(QBrush(QColor("red"), Qt::SolidPattern)); + //painter->setBackgroundMode(Qt::OpaqueMode); + painter->drawEllipse(unreadRect); + painter->setBackgroundMode(Qt::TransparentMode); + painter->setPen(QColor("white")); + drawElidedText(painter, unreadRect, QString("%1").arg(unreadCount), Qt::AlignCenter); + } + + painter->restore(); +} + +QSize DelegateCommons::contactSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const { + int heightByAvatar = avatarSize + verticalMargin * 2; + QFontMetrics nameMetrics(nameFont); + QFontMetrics statusMetrics(detailFont); + int sizeByText = 2 * verticalMargin + nameMetrics.height() + statusMetrics.height(); + //Doesn't work, yay! FIXME: why? + //QSize size = (option.state & QStyle::State_Selected) ? QSize(150, 80) : QSize(150, avatarSize_ + margin_ * 2); + //qDebug() << "Returning size" << size; + return QSize(150, sizeByText > heightByAvatar ? sizeByText : heightByAvatar); +} const int DelegateCommons::horizontalMargin = 2; const int DelegateCommons::verticalMargin = 2; const int DelegateCommons::farLeftMargin = 2; +const int DelegateCommons::avatarSize = 20; +const int DelegateCommons::presenceIconHeight = 16; +const int DelegateCommons::presenceIconWidth = 16; +const int DelegateCommons::unreadCountSize = 16; diff --git a/Swift/QtUI/Roster/DelegateCommons.h b/Swift/QtUI/Roster/DelegateCommons.h index 9213ef5..e5e4ff9 100644 --- a/Swift/QtUI/Roster/DelegateCommons.h +++ b/Swift/QtUI/Roster/DelegateCommons.h @@ -11,6 +11,8 @@ #include <QPainter> #include <QRect> #include <QString> +#include <QIcon> +#include <QStyleOptionViewItem> namespace Swift { class DelegateCommons { @@ -21,7 +23,10 @@ namespace Swift { detailFont.setPointSize(nameFont.pointSize() - detailFontSizeDrop); } - static void drawElidedText(QPainter* painter, const QRect& region, const QString& text); + static void drawElidedText(QPainter* painter, const QRect& region, const QString& text, int flags = Qt::AlignTop); + + QSize contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + void paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QColor& nameColor, const QString& avatarPath, const QIcon& presenceIcon, const QString& name, const QString& statusText, int unreadCount) const; int detailFontSizeDrop; QFont nameFont; @@ -29,5 +34,9 @@ namespace Swift { static const int horizontalMargin; static const int verticalMargin; static const int farLeftMargin; + static const int avatarSize; + static const int presenceIconHeight; + static const int presenceIconWidth; + static const int unreadCountSize; }; } diff --git a/Swift/QtUI/Roster/QtTreeWidgetItem.cpp b/Swift/QtUI/Roster/QtTreeWidgetItem.cpp deleted file mode 100644 index fcd8691..0000000 --- a/Swift/QtUI/Roster/QtTreeWidgetItem.cpp +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#include "Swift/QtUI/Roster/QtTreeWidgetItem.h" -#include "Swift/QtUI/Roster/QtTreeWidget.h" - -#include <qdebug.h> -#include <QtAlgorithms> -#include <algorithm> - -namespace Swift { - -QtTreeWidgetItem::QtTreeWidgetItem(QtTreeWidgetItem* parentItem) : QObject(), textColor_(0,0,0), backgroundColor_(255,255,255) { - parent_ = parentItem; - shown_ = true; - expanded_ = true; -} - - -void QtTreeWidgetItem::setText(const std::string& text) { - displayName_ = P2QSTRING(text); - displayNameLower_ = displayName_.toLower(); - emit changed(this); -} - -void QtTreeWidgetItem::setStatusText(const std::string& text) { - statusText_ = P2QSTRING(text); - emit changed(this); -} - -void QtTreeWidgetItem::setAvatarPath(const std::string& path) { - avatar_ = QIcon(P2QSTRING(path)); - emit changed(this); -} - -void QtTreeWidgetItem::setStatusShow(StatusShow::Type show) { - statusShowType_ = show; - int color = 0; - switch (show) { - case StatusShow::Online: color = 0x000000; mergedShowType_ = StatusShow::Online; break; - case StatusShow::Away: color = 0x336699; mergedShowType_ = StatusShow::Away; break; - case StatusShow::XA: color = 0x336699; mergedShowType_ = StatusShow::Away; break; - case StatusShow::FFC: color = 0x000000; mergedShowType_ = StatusShow::Online; break; - case StatusShow::DND: color = 0x990000; mergedShowType_ = show; break; - case StatusShow::None: color = 0x7F7F7F; mergedShowType_ = show; break; - } - setTextColor(color); - emit changed(this); -} - -void QtTreeWidgetItem::setTextColor(unsigned long color) { - textColor_ = QColor( - ((color & 0xFF0000)>>16), - ((color & 0xFF00)>>8), - (color & 0xFF)); -} - -void QtTreeWidgetItem::setBackgroundColor(unsigned long color) { - backgroundColor_ = QColor( - ((color & 0xFF0000)>>16), - ((color & 0xFF00)>>8), - (color & 0xFF)); -} - -void QtTreeWidgetItem::setExpanded(bool expanded) { - expanded_ = expanded; - emit changed(this); -} - -void QtTreeWidgetItem::hide() { - shown_ = false; - emit changed(this); -} - -void QtTreeWidgetItem::show() { - shown_ = true; - emit changed(this); -} - -bool QtTreeWidgetItem::isShown() { - return isContact() ? shown_ : shownChildren_.size() > 0; -} - -QWidget* QtTreeWidgetItem::getCollapsedRosterWidget() { - QWidget* widget = new QWidget(); - return widget; -} - -QWidget* QtTreeWidgetItem::getExpandedRosterWidget() { - QWidget* widget = new QWidget(); - return widget; -} - -QtTreeWidgetItem::~QtTreeWidgetItem() { - //It's possible (due to the way the roster deletes items in unknown order when it is deleted) - // That the children will be deleted before the groups, or that the groups are deleted - // before the children. If the children are deleted first, they will let the parent know that - // They've been deleted. If the parent is deleted first, it must tell the children not to - // tell it when they're deleted. Everything will be deleted in the end, because all the - // widgets are owned by the Roster in Swiften. - if (parent_) { - parent_->removeChild(this); - } - - for (int i = 0; i < children_.size(); i++) { - children_[i]->parentItemHasBeenDeleted(); - } -} - -void QtTreeWidgetItem::parentItemHasBeenDeleted() { - parent_ = NULL; -} - -QtTreeWidgetItem* QtTreeWidgetItem::getParentItem() { - return parent_; -} - -void QtTreeWidgetItem::addChild(QtTreeWidgetItem* child) { - children_.append(child); - connect(child, SIGNAL(changed(QtTreeWidgetItem*)), this, SLOT(handleChanged(QtTreeWidgetItem*))); - handleChanged(child); -} - -void QtTreeWidgetItem::removeChild(QtTreeWidgetItem* child) { - children_.removeAll(child); - handleChanged(this); -} - -void bubbleSort(QList<QtTreeWidgetItem*>& list) { - bool done = false; - for (int i = 0; i < list.size() - 1 && !done; i++) { - done = true; - for (int j = i + 1; j < list.size(); j++) { - if (*(list[j]) < *(list[j - 1])) { - done = false; - QtTreeWidgetItem* lower = list[j]; - list[j] = list[j - 1]; - list[j - 1] = lower; - } - } - } -} - -void QtTreeWidgetItem::handleChanged(QtTreeWidgetItem* child) { - /*We don't use the much faster qStableSort because it causes changed(child) and all sorts of nasty recursion*/ - //qStableSort(children_.begin(), children_.end(), itemLessThan); - //bubbleSort(children_); - std::stable_sort(children_.begin(), children_.end(), itemLessThan); - shownChildren_.clear(); - for (int i = 0; i < children_.size(); i++) { - if (children_[i]->isShown()) { - shownChildren_.append(children_[i]); - } - } - emit changed(child); -} - -int QtTreeWidgetItem::rowCount() { - //qDebug() << "Returning size of " << children_.size() << " for item " << displayName_; - return shownChildren_.size(); -} - -int QtTreeWidgetItem::rowOf(QtTreeWidgetItem* item) { - return shownChildren_.indexOf(item); -} - -int QtTreeWidgetItem::row() { - return parent_ ? parent_->rowOf(this) : 0; -} - -QtTreeWidgetItem* QtTreeWidgetItem::getItem(int row) { - //qDebug() << "Returning row " << row << " from item " << displayName_; - Q_ASSERT(row >= 0); - Q_ASSERT(row < rowCount()); - return shownChildren_[row]; -} - - -QVariant QtTreeWidgetItem::data(int role) { - if (!isContact()) { - setTextColor(0xFFFFFF); - setBackgroundColor(0x969696); - } - switch (role) { - case Qt::DisplayRole: return displayName_; - case Qt::TextColorRole: return textColor_; - case Qt::BackgroundColorRole: return backgroundColor_; - case Qt::ToolTipRole: return isContact() ? toolTipString() : QVariant(); - case StatusTextRole: return statusText_; - case AvatarRole: return avatar_; - case PresenceIconRole: return getPresenceIcon(); - default: return QVariant(); - } -} - -QString QtTreeWidgetItem::toolTipString() { - return displayName_ + "\n " + statusText_; -} - -QIcon QtTreeWidgetItem::getPresenceIcon() { - QString iconString; - switch (statusShowType_) { - case StatusShow::Online: iconString = "online";break; - case StatusShow::Away: iconString = "away";break; - case StatusShow::XA: iconString = "away";break; - case StatusShow::FFC: iconString = "online";break; - case StatusShow::DND: iconString = "dnd";break; - case StatusShow::None: iconString = "offline";break; - } - return QIcon(":/icons/" + iconString + ".png"); -} - -bool QtTreeWidgetItem::isContact() const { - return children_.size() == 0; -} - -bool QtTreeWidgetItem::isExpanded() { - return expanded_; -} - -bool QtTreeWidgetItem::operator<(const QtTreeWidgetItem& item) const { - if (isContact()) { - if (!item.isContact()) { - return false; - } - return getStatusShowMerged() == item.getStatusShowMerged() ? getLowerName() < item.getLowerName() : getStatusShowMerged() < item.getStatusShowMerged(); - } else { - if (item.isContact()) { - return true; - } - return getLowerName() < item.getLowerName(); - } -} - -bool itemLessThan(QtTreeWidgetItem* left, QtTreeWidgetItem* right) { - return *left < *right; -} - -} diff --git a/Swift/QtUI/Roster/QtTreeWidgetItem.h b/Swift/QtUI/Roster/QtTreeWidgetItem.h deleted file mode 100644 index 6855989..0000000 --- a/Swift/QtUI/Roster/QtTreeWidgetItem.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2010 Kevin Smith - * Licensed under the GNU General Public License v3. - * See Documentation/Licenses/GPLv3.txt for more information. - */ - -#pragma once - -#include <QColor> -#include <QVariant> -#include <string> - -#include "Swiften/Roster/TreeWidgetFactory.h" -#include "Swiften/Roster/TreeWidget.h" -#include "Swiften/Roster/TreeWidgetItem.h" -#include "Swift/QtUI/QtSwiftUtil.h" - -namespace Swift { - enum RosterRoles { - StatusTextRole = Qt::UserRole, - AvatarRole = Qt::UserRole + 1, - PresenceIconRole = Qt::UserRole + 2, - StatusShowTypeRole = Qt::UserRole + 3 - }; - -class QtTreeWidget; -class QtTreeWidgetItem : public QObject, public TreeWidgetItem { - Q_OBJECT - public: - ~QtTreeWidgetItem(); - void addChild(QtTreeWidgetItem* child); - void removeChild(QtTreeWidgetItem* child); - QtTreeWidgetItem* getParentItem(); - int rowCount(); - int rowOf(QtTreeWidgetItem* item); - int row(); - QtTreeWidgetItem* getItem(int row); - QVariant data(int role); - QIcon getPresenceIcon(); - QtTreeWidgetItem(QtTreeWidgetItem* parentItem); - void setText(const std::string& text); - void setAvatarPath(const std::string& path); - void setStatusText(const std::string& text); - void setStatusShow(StatusShow::Type show); - void setTextColor(unsigned long color); - void setBackgroundColor(unsigned long color); - void setExpanded(bool b); - void parentItemHasBeenDeleted(); - void hide(); - void show(); - bool isShown(); - bool isContact() const; - bool isExpanded(); - const QString& getName() const {return displayName_;}; - const QString& getLowerName() const {return displayNameLower_;}; - StatusShow::Type getStatusShow() const {return statusShowType_;}; - StatusShow::Type getStatusShowMerged() const {return mergedShowType_;}; - - QWidget* getCollapsedRosterWidget(); - QWidget* getExpandedRosterWidget(); - bool operator<(const QtTreeWidgetItem& item) const; - - signals: - void changed(QtTreeWidgetItem*); - private slots: - void handleChanged(QtTreeWidgetItem* item); - private: - QString toolTipString(); - QList<QtTreeWidgetItem*> children_; - QList<QtTreeWidgetItem*> shownChildren_; - QtTreeWidgetItem* parent_; - QString displayName_; - QString displayNameLower_; - QString statusText_; - QColor textColor_; - QColor backgroundColor_; - QVariant avatar_; - bool shown_; - bool expanded_; - StatusShow::Type statusShowType_; - StatusShow::Type mergedShowType_; -}; - -bool itemLessThan(QtTreeWidgetItem* left, QtTreeWidgetItem* right); - -} diff --git a/Swift/QtUI/Roster/RosterDelegate.cpp b/Swift/QtUI/Roster/RosterDelegate.cpp index aaa6236..e40907a 100644 --- a/Swift/QtUI/Roster/RosterDelegate.cpp +++ b/Swift/QtUI/Roster/RosterDelegate.cpp @@ -12,7 +12,6 @@ #include <QBrush> #include <QFontMetrics> #include <QPainterPath> -#include <QFileInfo> #include <QPolygon> #include <qdebug.h> #include <QBitmap> @@ -22,7 +21,6 @@ #include "QtTreeWidget.h" #include "RosterModel.h" -#include "QtScaledAvatarCache.h" namespace Swift { @@ -43,15 +41,8 @@ QSize RosterDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelI return contactSizeHint(option, index); } -QSize RosterDelegate::contactSizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/ ) const { - int heightByAvatar = avatarSize_ + common_.verticalMargin * 2; - QFontMetrics nameMetrics(common_.nameFont); - QFontMetrics statusMetrics(common_.detailFont); - int sizeByText = 2 * common_.verticalMargin + nameMetrics.height() + statusMetrics.height(); - //Doesn't work, yay! FIXME: why? - //QSize size = (option.state & QStyle::State_Selected) ? QSize(150, 80) : QSize(150, avatarSize_ + margin_ * 2); - //qDebug() << "Returning size" << size; - return QSize(150, sizeByText > heightByAvatar ? sizeByText : heightByAvatar); +QSize RosterDelegate::contactSizeHint(const QStyleOptionViewItem& option, const QModelIndex& index ) const { + return common_.contactSizeHint(option, index); } void RosterDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { @@ -70,70 +61,18 @@ void RosterDelegate::paintGroup(QPainter* painter, const QStyleOptionViewItem& o } void RosterDelegate::paintContact(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - //qDebug() << "painting" << index.data(Qt::DisplayRole).toString(); - painter->save(); - //QStyledItemDelegate::paint(painter, option, index); - //initStyleOption(option, index); - QRect fullRegion(option.rect); - if ( option.state & QStyle::State_Selected ) { - painter->fillRect(fullRegion, option.palette.highlight()); - painter->setPen(option.palette.highlightedText().color()); - } else { - QColor nameColor = index.data(Qt::TextColorRole).value<QColor>(); - painter->setPen(QPen(nameColor)); - } - - QRect presenceIconRegion(QPoint(common_.farLeftMargin, fullRegion.top()), QSize(presenceIconWidth_, fullRegion.height() - common_.verticalMargin)); - - int calculatedAvatarSize = presenceIconRegion.height(); - //This overlaps the presenceIcon, so must be painted first - QRect avatarRegion(QPoint(presenceIconRegion.right() - presenceIconWidth_ / 2, presenceIconRegion.top()), QSize(calculatedAvatarSize, calculatedAvatarSize)); - - QPixmap avatarPixmap; + QColor nameColor = index.data(Qt::TextColorRole).value<QColor>(); + QString avatarPath; if (index.data(AvatarRole).isValid() && !index.data(AvatarRole).value<QString>().isNull()) { - QString avatarPath = index.data(AvatarRole).value<QString>(); - QString scaledAvatarPath = QtScaledAvatarCache(avatarRegion.height()).getScaledAvatarPath(avatarPath); - if (QFileInfo(scaledAvatarPath).exists()) { - avatarPixmap.load(scaledAvatarPath); - } + avatarPath = index.data(AvatarRole).value<QString>(); } - if (avatarPixmap.isNull()) { - avatarPixmap = QPixmap(":/icons/avatar.png").scaled(avatarRegion.height(), avatarRegion.width(), Qt::KeepAspectRatio, Qt::SmoothTransformation); - } - - painter->drawPixmap(avatarRegion.topLeft() + QPoint(((avatarRegion.width() - avatarPixmap.width()) / 2), (avatarRegion.height() - avatarPixmap.height()) / 2), avatarPixmap); - - //Paint the presence icon over the top of the avatar QIcon presenceIcon = index.data(PresenceIconRole).isValid() && !index.data(PresenceIconRole).value<QIcon>().isNull() - ? index.data(PresenceIconRole).value<QIcon>() - : QIcon(":/icons/offline.png"); - presenceIcon.paint(painter, presenceIconRegion, Qt::AlignBottom | Qt::AlignHCenter); - - QFontMetrics nameMetrics(common_.nameFont); - painter->setFont(common_.nameFont); - int extraFontWidth = nameMetrics.width("H"); - int leftOffset = avatarRegion.right() + common_.horizontalMargin * 2 + extraFontWidth / 2; - QRect textRegion(fullRegion.adjusted(leftOffset, 0, 0/*-leftOffset*/, 0)); - - int nameHeight = nameMetrics.height() + common_.verticalMargin; - QRect nameRegion(textRegion.adjusted(0, common_.verticalMargin, 0, 0)); - - DelegateCommons::drawElidedText(painter, nameRegion, index.data(Qt::DisplayRole).toString()); - - - painter->setFont(common_.detailFont); - painter->setPen(QPen(QColor(160,160,160))); - - QRect statusTextRegion(textRegion.adjusted(0, nameHeight, 0, 0)); - DelegateCommons::drawElidedText(painter, statusTextRegion, index.data(StatusTextRole).toString()); - - painter->restore(); + ? index.data(PresenceIconRole).value<QIcon>() + : QIcon(":/icons/offline.png"); + QString name = index.data(Qt::DisplayRole).toString(); + QString statusText = index.data(StatusTextRole).toString(); + common_.paintContact(painter, option, nameColor, avatarPath, presenceIcon, name, statusText, 0); } - -const int RosterDelegate::avatarSize_ = 20; -const int RosterDelegate::presenceIconHeight_ = 16; -const int RosterDelegate::presenceIconWidth_ = 16; - } diff --git a/Swift/QtUI/Roster/RosterDelegate.h b/Swift/QtUI/Roster/RosterDelegate.h index e6a16f2..253c11a 100644 --- a/Swift/QtUI/Roster/RosterDelegate.h +++ b/Swift/QtUI/Roster/RosterDelegate.h @@ -28,9 +28,5 @@ namespace Swift { DelegateCommons common_; GroupItemDelegate* groupDelegate_; QtTreeWidget* tree_; - static const int avatarSize_; - static const int presenceIconHeight_; - static const int presenceIconWidth_; - }; } diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript index 494731c..24299fb 100644 --- a/Swift/QtUI/SConscript +++ b/Swift/QtUI/SConscript @@ -25,16 +25,12 @@ myenv.MergeFlags(env["SWIFT_CONTROLLERS_FLAGS"]) myenv.MergeFlags(env["SWIFTOOLS_FLAGS"]) if myenv["HAVE_XSS"] : myenv.MergeFlags(env["XSS_FLAGS"]) +if env["PLATFORM"] == "posix" : + myenv.Append(LIBS = ["X11"]) if myenv["HAVE_SPARKLE"] : myenv.MergeFlags(env["SPARKLE_FLAGS"]) myenv.MergeFlags(env["SWIFTEN_FLAGS"]) -myenv.MergeFlags(env["LIBIDN_FLAGS"]) -myenv.MergeFlags(env["BOOST_FLAGS"]) -myenv.MergeFlags(env.get("SQLITE_FLAGS", {})) -myenv.MergeFlags(env["ZLIB_FLAGS"]) -myenv.MergeFlags(env["OPENSSL_FLAGS"]) -myenv.MergeFlags(env.get("LIBXML_FLAGS", "")) -myenv.MergeFlags(env.get("EXPAT_FLAGS", "")) +myenv.MergeFlags(env["SWIFTEN_DEP_FLAGS"]) if myenv.get("HAVE_GROWL", False) : myenv.MergeFlags(myenv["GROWL_FLAGS"]) myenv.Append(CPPDEFINES = ["HAVE_GROWL"]) @@ -59,7 +55,7 @@ if env["PLATFORM"] == "win32" : myenv.Append(LINKFLAGS = ["/SUBSYSTEM:WINDOWS"]) myenv.Append(LIBS = "qtmain") -myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateDefaultTheme(myenv.Dir("../resources/themes/Default")))) +myenv.WriteVal("DefaultTheme.qrc", myenv.Value(generateDefaultTheme(myenv.Dir("#/Swift/resources/themes/Default")))) sources = [ "main.cpp", @@ -77,6 +73,7 @@ sources = [ "QtStatusWidget.cpp", "QtScaledAvatarCache.cpp", "QtSwift.cpp", + "QtURIHandler.cpp", "QtChatView.cpp", "QtChatTheme.cpp", "QtChatTabs.cpp", @@ -87,6 +84,7 @@ sources = [ "QtTabWidget.cpp", "QtTextEdit.cpp", "QtXMLConsoleWidget.cpp", + "QtAdHocCommandWindow.cpp", "QtUtilities.cpp", "QtBookmarkDetailWindow.cpp", "QtAddBookmarkWindow.cpp", @@ -97,6 +95,7 @@ sources = [ "MessageSnippet.cpp", "SystemMessageSnippet.cpp", "QtElidingLabel.cpp", + "QtFormWidget.cpp", "QtLineEdit.cpp", "QtJoinMUCWindow.cpp", "Roster/RosterModel.cpp", @@ -114,6 +113,7 @@ sources = [ "ChatList/ChatListModel.cpp", "ChatList/ChatListDelegate.cpp", "ChatList/ChatListMUCItem.cpp", + "ChatList/ChatListRecentItem.cpp", "MUCSearch/QtMUCSearchWindow.cpp", "MUCSearch/MUCSearchModel.cpp", "MUCSearch/MUCSearchRoomItem.cpp", @@ -136,20 +136,31 @@ sources = [ myenv["SWIFT_VERSION"] = Version.getBuildVersion(env.Dir("#").abspath, "swift") if env["PLATFORM"] == "win32" : - myenv.RES("../resources/Windows/Swift.rc") + res = myenv.RES("#/Swift/resources/Windows/Swift.rc") + # For some reason, SCons isn't picking up the dependency correctly + # Adding it explicitly until i figure out why + myenv.Depends(res, "../Controllers/BuildVersion.h") sources += [ "WindowsNotifier.cpp", - "../resources/Windows/Swift.res" + "#/Swift/resources/Windows/Swift.res" ] if env["PLATFORM"] == "posix" : - sources += ["FreeDesktopNotifier.cpp"] + sources += [ + "FreeDesktopNotifier.cpp", + "QtDBUSURIHandler.cpp", + ] if env["PLATFORM"] == "darwin" or env["PLATFORM"] == "win32" : swiftProgram = myenv.Program("Swift", sources) else : swiftProgram = myenv.Program("swift", sources) +if env["PLATFORM"] != "darwin" and env["PLATFORM"] != "win32" : + openURIProgram = myenv.Program("swift-open-uri", "swift-open-uri.cpp") +else : + openURIProgram = [] + myenv.Uic4("MUCSearch/QtMUCSearchWindow.ui") myenv.Uic4("UserSearch/QtUserSearchWizard.ui") myenv.Uic4("UserSearch/QtUserSearchFirstPage.ui") @@ -162,7 +173,7 @@ myenv.Qrc("Swift.qrc") # Resources commonResources = { - "": ["../resources/sounds"] + "": ["#/Swift/resources/sounds"] } ################################################################################ @@ -171,16 +182,16 @@ commonResources = { # Collect available languages translation_languages = [] -for file in os.listdir(Dir("../Translations").abspath) : +for file in os.listdir(Dir("#/Swift/Translations").abspath) : if file.startswith("swift_") and file.endswith(".ts") : translation_languages.append(file[6:-3]) # Generate translation modules -translation_sources = [env.File("../Translations/swift.ts").abspath] +translation_sources = [env.File("#/Swift/Translations/swift.ts").abspath] translation_modules = [] for lang in translation_languages : - translation_resource = "../resources/translations/swift_" + lang + ".qm" - translation_source = "../Translations/swift_" + lang + ".ts" + translation_resource = "#/Swift/resources/translations/swift_" + lang + ".qm" + translation_source = "#/Swift/Translations/swift_" + lang + ".ts" translation_sources.append(env.File(translation_source).abspath) translation_modules.append(env.File(translation_resource).abspath) myenv.Qm(translation_resource, translation_source) @@ -214,20 +225,20 @@ if env["PLATFORM"] == "darwin" : frameworks.append(env["SPARKLE_FRAMEWORK"]) if env["HAVE_GROWL"] : frameworks.append(env["GROWL_FRAMEWORK"]) - commonResources[""] = commonResources.get("", []) + ["../resources/MacOSX/Swift.icns"] - app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks) + commonResources[""] = commonResources.get("", []) + ["#/Swift/resources/MacOSX/Swift.icns"] + app = myenv.AppBundle("Swift", version = myenv["SWIFT_VERSION"], resources = commonResources, frameworks = frameworks, handlesXMPPURIs = True) if env["DIST"] : myenv.Command(["Swift-${SWIFT_VERSION}.dmg"], [app], ["Swift/Packaging/MacOSX/package.sh " + app.path + " Swift/Packaging/MacOSX/Swift.dmg.gz $TARGET $QTDIR"]) if env.get("SWIFT_INSTALLDIR", "") : - env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "bin"), swiftProgram) - env.InstallAs(os.path.join(env["SWIFT_INSTALLDIR"], "share", "pixmaps", "swift.xpm"), "../resources/logo/logo-icon-32.xpm") + env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "bin"), swiftProgram + openURIProgram) + env.InstallAs(os.path.join(env["SWIFT_INSTALLDIR"], "share", "pixmaps", "swift.xpm"), "#/Swift/resources/logo/logo-icon-32.xpm") icons_path = os.path.join(env["SWIFT_INSTALLDIR"], "share", "icons", "hicolor") - env.InstallAs(os.path.join(icons_path, "32x32", "apps", "swift.xpm"), "../resources/logo/logo-icon-32.xpm") - env.InstallAs(os.path.join(icons_path, "scalable", "apps", "swift.svg"), "../resources/logo/logo-icon.svg") + env.InstallAs(os.path.join(icons_path, "32x32", "apps", "swift.xpm"), "#/Swift/resources/logo/logo-icon-32.xpm") + env.InstallAs(os.path.join(icons_path, "scalable", "apps", "swift.svg"), "#/Swift/resources/logo/logo-icon.svg") for i in ["16", "22", "24", "64", "128"] : - env.InstallAs(os.path.join(icons_path, i + "x" + i, "apps", "swift.png"), "../resources/logo/logo-icon-" + i + ".png") - env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "applications"), "../resources/swift.desktop") + env.InstallAs(os.path.join(icons_path, i + "x" + i, "apps", "swift.png"), "#/Swift/resources/logo/logo-icon-" + i + ".png") + env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "applications"), "#/Swift/resources/swift.desktop") for dir, resource in commonResources.items() : env.Install(os.path.join(env["SWIFT_INSTALLDIR"], "share", "swift", dir), resource) @@ -236,7 +247,7 @@ if env["PLATFORM"] == "win32" : commonResources[""] = commonResources.get("", []) + [ os.path.join(env["OPENSSL_DIR"], "bin", "ssleay32.dll"), os.path.join(env["OPENSSL_DIR"], "bin", "libeay32.dll"), - "../resources/images", + "#/Swift/resources/images", ] myenv.WindowsBundle("Swift", resources = commonResources, diff --git a/Swift/QtUI/main.cpp b/Swift/QtUI/main.cpp index 0146769..2eef379 100644 --- a/Swift/QtUI/main.cpp +++ b/Swift/QtUI/main.cpp @@ -44,7 +44,7 @@ int main(int argc, char* argv[]) { boost::program_options::variables_map vm; try { boost::program_options::store(boost::program_options::parse_command_line(argc, argv, desc), vm); - } catch (boost::program_options::unknown_option option) { + } catch (const boost::program_options::unknown_option& option) { #if BOOST_VERSION >= 104200 std::cout << "Ignoring unknown option " << option.get_option_name() << " but continuing." << std::endl; #else diff --git a/Swift/QtUI/swift-open-uri.cpp b/Swift/QtUI/swift-open-uri.cpp new file mode 100644 index 0000000..2d5ef19 --- /dev/null +++ b/Swift/QtUI/swift-open-uri.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011 Remko Tronçon + * Licensed under the GNU General Public License v3. + * See Documentation/Licenses/GPLv3.txt for more information. + */ + +#include <QCoreApplication> +#include <QDBusConnection> +#include <QDBusMessage> +#include <iostream> + +int main(int argc, char* argv[]) { + QCoreApplication app(argc, argv); + + QDBusConnection bus = QDBusConnection::sessionBus(); + if (!bus.isConnected()) { + return -1; + } + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " uri" << std::endl; + return -1; + } + + QDBusMessage msg = QDBusMessage::createMethodCall("im.swift.Swift.URIHandler", "/", "im.swift.Swift.URIHandler", "openURI"); + msg << argv[1]; + + bus.call(msg); + + return 0; +} |