Commit 3ddffbd2 authored by Robert Knight's avatar Robert Knight
Browse files

Re-implement 'Send Input to All' (now Edit -> Copy Input To...)

Improvements in the KDE 4 version:

- Input can be copied to all other sessions or only a subset of sessions,
  selection is via a filter-able list which appears when 'Copy Input To...' is clicked.
- Input can be sent from a tab in one window to a tab in another window
  (all sessions in all windows are displayed in the filter window)

BUG: 152072
BUG: 77682

Squashed commit of the following:

commit 3184e1958d3aa7e7574081df32814dbe44591c9e
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 19:24:22 2008 +0100

    Remove debugging messages.

commit 31afd9b2a3a327ca3e43deff47415de41a1ad74e
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 19:16:33 2008 +0100

    Copy input dialog.  Hide 'Number' column and header.  Show the check box in the title column.  Focus the search filter before showing the dialog.

commit 8c74e736d322e74d7af812a14500d748e748a224
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 19:03:59 2008 +0100

    Remove sessions from the internal checked/fixed sets when they are removed.  Fix crashes if sessions exited while SessionController was showing a dialog for that session.  Add QPointer<T> guards around the dialog exec() methods.

commit e4085069c2ff8cedb987639f50b73a3c679319f6
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 18:44:40 2008 +0100

    Remove sessions from the SessionGroup and SessionListModel when they terminate.

commit 29db3a3dd84f392ee333918e261bd6bcd75fd062
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 18:39:24 2008 +0100

    Use an extended rather than single selection in CopyInputDialog

commit 3f93507405053c84e09337225fa6e2a582776264
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 17:54:22 2008 +0100

    Copy Input Dialog:  Show tab text in the Title column.  Show clear button in filter line edit.

commit 0619f7ad72a1a346118a0174e5aee01ce94ddc52
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 17:40:07 2008 +0100

    Copy Input Dialog:  Add buttons to select and deselect all visible sessions.

commit 126afb3d394894a0325c5772fb32a25380932c44
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 17:23:07 2008 +0100

    Show the master session as checked and disabled.

commit c559bd5f5bcc59adecc3f29ee5b16200adb37b09
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 17:20:40 2008 +0100

    Create a SessionGroup in the SessionController when copyInputToAll() is called and use the CopyInputDialog to allow the user to choose which sessions the current one copies to.

commit 64c12e58a582a17b5cdeddcc8f9df7a89885f9e5
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 17:19:47 2008 +0100

    Display session list with check boxes and filter to select sessions to copy input to.  Show master session as a disabled, checked item.

commit 43eb5ccdfc768b42c5e650b6e56d1e1120ab5c5b
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 17:19:06 2008 +0100

    Add header data and Session* pointer to items in SessionListModel.

commit a8236a6658e1540a924aa6467dc90aeed346a0be
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 17:18:28 2008 +0100

    Remove connection to removed slot.

commit 03990fa244159b50152cc721a0fe4e403551d47c
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 17:17:54 2008 +0100

    Make SessionGroup take a QObject* parent argument in the constructor.

commit e2f91001c09a8422fb44ad58cf95f8dcba22c063
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 15:25:26 2008 +0100

    Add CopyInputDialog to build.

commit 3b33e1d6abdde56bbafd2bbd64c89c008a41d096
Author: Robert Knight <robertknight@gmail.com>
Date:   Sun Apr 13 14:32:36 2008 +0100

    Add header and implementation files for 'Copy Input To' dialog.

commit 9ebc36e3f09865f29702768cbb17279054e8cb4c
Author: Robert Knight <robertknight@gmail.com>
Date:   Thu Apr 10 07:04:48 2008 +0100

    Add a dialog for selecting sessions to copy input to.

svn path=/trunk/KDE/kdebase/apps/konsole/; revision=796545
parent cf93c4c5
......@@ -11,7 +11,7 @@
<Action name="paste" group="session-edit-operations" />
<Action name="rename-session" group="session-edit-operations" />
<Separator group="session-edit-operations"/>
<Action name="send-input-to-all" group="session-edit-operations"/>
<Action name="copy-input-to" group="session-edit-operations"/>
<Separator group="session-edit-operations"/>
<Action name="clear" group="session-edit-operations" />
<Action name="clear-and-reset" group="session-edit-operations" />
......
......@@ -29,6 +29,7 @@ set(konsole_KDEINIT_SRCS
BookmarkHandler.cpp
ColorScheme.cpp
ColorSchemeEditor.cpp
CopyInputDialog.cpp
EditProfileDialog.cpp
Emulation.cpp
Filter.cpp
......@@ -69,6 +70,7 @@ set(konsole_KDEINIT_SRCS
kde4_add_ui_files(konsole_KDEINIT_SRCS
ColorSchemeEditor.ui
CopyInputDialog.ui
EditProfileDialog.ui
KeyBindingEditor.ui
ManageProfilesDialog.ui
......@@ -110,6 +112,7 @@ set(konsolepart_PART_SRCS
BookmarkHandler.cpp
ColorScheme.cpp
ColorSchemeEditor.cpp
CopyInputDialog.cpp
EditProfileDialog.cpp
Emulation.cpp
Filter.cpp
......
/*
Copyright 2008 by Robert Knight <robertknight@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
// Own
#include "CopyInputDialog.h"
// Qt
#include <QtGui/QSortFilterProxyModel>
#include <QtGui/QHeaderView>
// Konsole
#include "ui_CopyInputDialog.h"
using namespace Konsole;
CopyInputDialog::CopyInputDialog(QWidget* parent)
: KDialog(parent)
{
setCaption(i18n("Copy Input"));
setButtons( KDialog::Ok | KDialog::Cancel );
_ui = new Ui::CopyInputDialog();
_ui->setupUi(mainWidget());
connect(_ui->selectAllButton,SIGNAL(clicked()),this,SLOT(selectAll()));
connect(_ui->deselectAllButton,SIGNAL(clicked()),this,SLOT(deselectAll()));
_ui->filterEdit->setClearButtonShown(true);
_ui->filterEdit->setFocus();
_model = new CheckableSessionModel(parent);
_model->setCheckColumn(1);
_model->setSessions(SessionManager::instance()->sessions());
QSortFilterProxyModel* filterProxyModel = new QSortFilterProxyModel(this);
filterProxyModel->setDynamicSortFilter(true);
filterProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
filterProxyModel->setSourceModel(_model);
filterProxyModel->setFilterKeyColumn(-1);
connect(_ui->filterEdit,SIGNAL(textChanged(QString)),filterProxyModel,
SLOT(setFilterFixedString(QString)));
_ui->sessionList->setModel(filterProxyModel);
_ui->sessionList->setColumnHidden(0,true); // Hide number column
_ui->sessionList->header()->hide();
}
void CopyInputDialog::setChosenSessions(const QSet<Session*>& sessions)
{
QSet<Session*> checked = sessions;
if (_masterSession)
checked.insert(_masterSession);
_model->setCheckedSessions(checked);
}
QSet<Session*> CopyInputDialog::chosenSessions() const
{
return _model->checkedSessions();
}
void CopyInputDialog::setMasterSession(Session* session)
{
if (_masterSession)
_model->setCheckable(_masterSession,true);
_model->setCheckable(session,false);
QSet<Session*> checked = _model->checkedSessions();
checked.insert(session);
_model->setCheckedSessions(checked);
_masterSession = session;
}
void CopyInputDialog::setSelectionChecked(bool checked)
{
QAbstractItemModel* model = _ui->sessionList->model();
int rows = model->rowCount();
QModelIndexList selected = _ui->sessionList->selectionModel()->selectedIndexes();
if (selected.count() > 1)
{
foreach(QModelIndex index,selected)
setRowChecked(index.row(),checked);
}
else
{
for (int i=0;i<rows;i++)
setRowChecked(i,checked);
}
}
void CopyInputDialog::setRowChecked(int row, bool checked)
{
QAbstractItemModel* model = _ui->sessionList->model();
QModelIndex index = model->index(row,0);
if (checked)
model->setData(index,(int)Qt::Checked,Qt::CheckStateRole);
else
model->setData(index,(int)Qt::Unchecked,Qt::CheckStateRole);
}
CheckableSessionModel::CheckableSessionModel(QObject* parent)
: SessionListModel(parent)
, _checkColumn(0)
{
}
void CheckableSessionModel::setCheckColumn(int column)
{
_checkColumn = column;
reset();
}
Qt::ItemFlags CheckableSessionModel::flags(const QModelIndex& index) const
{
Session* session = (Session*)index.internalPointer();
if (_fixedSessions.contains(session))
return SessionListModel::flags(index) & ~Qt::ItemIsEnabled;
else
return SessionListModel::flags(index) | Qt::ItemIsUserCheckable;
}
QVariant CheckableSessionModel::data(const QModelIndex& index, int role) const
{
if (role == Qt::CheckStateRole && index.column() == _checkColumn)
{
Session* session = (Session*)index.internalPointer();
if (_checkedSessions.contains(session))
return QVariant::fromValue((int)Qt::Checked);
else
return QVariant::fromValue((int)Qt::Unchecked);
}
else
return SessionListModel::data(index,role);
}
bool CheckableSessionModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (role == Qt::CheckStateRole && index.column() == _checkColumn)
{
Session* session = (Session*)index.internalPointer();
if (_fixedSessions.contains(session))
return false;
if (value.value<int>() == Qt::Checked)
_checkedSessions.insert(session);
else
_checkedSessions.remove(session);
emit dataChanged(index,index);
return true;
}
else
return SessionListModel::setData(index,value,role);
}
void CheckableSessionModel::setCheckedSessions(const QSet<Session*> sessions)
{
_checkedSessions = sessions;
reset();
}
QSet<Session*> CheckableSessionModel::checkedSessions() const
{
return _checkedSessions;
}
void CheckableSessionModel::setCheckable(Session* session, bool checkable)
{
if (!checkable)
_fixedSessions.insert(session);
else
_fixedSessions.remove(session);
reset();
}
void CheckableSessionModel::sessionRemoved(Session* session)
{
_checkedSessions.remove(session);
_fixedSessions.remove(session);
}
/*
Copyright 2008 by Robert Knight <robertknight@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
*/
#ifndef COPYINPUTDIALOG
#define COPYINPUTDIALOG
// Qt
#include <QPointer>
// KDE
#include <KDialog>
// Konsole
#include "SessionManager.h"
#include "Session.h"
namespace Ui
{
class CopyInputDialog;
}
namespace Konsole
{
class CheckableSessionModel;
class CopyInputDialog : public KDialog
{
Q_OBJECT
public:
CopyInputDialog(QWidget* parent = 0);
void setMasterSession(Session* master);
Session* masterSession() const;
void setChosenSessions(const QSet<Session*>& sessions);
QSet<Session*> chosenSessions() const;
private slots:
void selectAll() { setSelectionChecked(true); };
void deselectAll() { setSelectionChecked(false); };
private:
void setSelectionChecked(bool checked);
void setRowChecked(int row, bool checked);
Ui::CopyInputDialog* _ui;
CheckableSessionModel* _model;
QPointer<Session> _masterSession;
};
class CheckableSessionModel : public SessionListModel
{
Q_OBJECT
public:
CheckableSessionModel(QObject* parent);
void setCheckColumn(int column);
void setCheckable(Session* session, bool checkable);
void setCheckedSessions(const QSet<Session*> sessions);
QSet<Session*> checkedSessions() const;
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
virtual QVariant data(const QModelIndex& index, int role) const;
virtual bool setData(const QModelIndex& index, const QVariant& value, int role);
protected:
virtual void sessionRemoved(Session*);
private:
QSet<Session*> _checkedSessions;
QSet<Session*> _fixedSessions;
int _checkColumn;
};
}
#endif // COPYINPUTDIALOG
<ui version="4.0" >
<class>CopyInputDialog</class>
<widget class="QWidget" name="CopyInputDialog" >
<property name="geometry" >
<rect>
<x>0</x>
<y>0</y>
<width>363</width>
<height>223</height>
</rect>
</property>
<property name="windowTitle" >
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout" >
<item>
<layout class="QHBoxLayout" name="horizontalLayout" >
<item>
<widget class="QLabel" name="label" >
<property name="text" >
<string>Filter:</string>
</property>
</widget>
</item>
<item>
<widget class="KLineEdit" name="filterEdit" />
</item>
</layout>
</item>
<item>
<widget class="QTreeView" name="sessionList" >
<property name="selectionMode" >
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="rootIsDecorated" >
<bool>false</bool>
</property>
<property name="uniformRowHeights" >
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2" >
<item>
<widget class="QPushButton" name="selectAllButton" >
<property name="text" >
<string>Select All</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deselectAllButton" >
<property name="text" >
<string>Deselect All</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer" >
<property name="orientation" >
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0" >
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>KLineEdit</class>
<extends>QLineEdit</extends>
<header>klineedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
......@@ -952,8 +952,8 @@ int Session::processId() const
return _shellProcess->pid();
}
SessionGroup::SessionGroup()
: _masterMode(0)
SessionGroup::SessionGroup(QObject* parent)
: QObject(parent), _masterMode(0)
{
}
SessionGroup::~SessionGroup()
......@@ -967,6 +967,8 @@ bool SessionGroup::masterStatus(Session* session) const { return _sessions[sessi
void SessionGroup::addSession(Session* session)
{
connect(session,SIGNAL(finished()),this,SLOT(sessionFinished()));
_sessions.insert(session,false);
QListIterator<Session*> masterIter(masters());
......@@ -976,6 +978,8 @@ void SessionGroup::addSession(Session* session)
}
void SessionGroup::removeSession(Session* session)
{
disconnect(session,SIGNAL(finished()),this,SLOT(sessionFinished()));
setMasterStatus(session,false);
QListIterator<Session*> masterIter(masters());
......@@ -985,6 +989,12 @@ void SessionGroup::removeSession(Session* session)
_sessions.remove(session);
}
void SessionGroup::sessionFinished()
{
Session* session = qobject_cast<Session*>(sender());
Q_ASSERT(session);
removeSession(session);
}
void SessionGroup::setMasterMode(int mode)
{
_masterMode = mode;
......@@ -1044,11 +1054,9 @@ void SessionGroup::setMasterStatus(Session* session , bool master)
}
void SessionGroup::connectPair(Session* master , Session* other)
{
kDebug() << k_funcinfo;
if ( _masterMode & CopyInputToAll )
{
kDebug() << "Connection session " << master->nameTitle() << "to" << other->nameTitle();
// kDebug() << "Connection session " << master->nameTitle() << "to" << other->nameTitle();
connect( master->emulation() , SIGNAL(sendData(const char*,int)) , other->emulation() ,
SLOT(sendString(const char*,int)) );
......@@ -1056,11 +1064,9 @@ void SessionGroup::connectPair(Session* master , Session* other)
}
void SessionGroup::disconnectPair(Session* master , Session* other)
{
kDebug() << k_funcinfo;
if ( _masterMode & CopyInputToAll )
{
kDebug() << "Disconnecting session " << master->nameTitle() << "from" << other->nameTitle();
// kDebug() << "Disconnecting session " << master->nameTitle() << "from" << other->nameTitle();
disconnect( master->emulation() , SIGNAL(sendData(const char*,int)) , other->emulation() ,
SLOT(sendString(const char*,int)) );
......
......@@ -567,7 +567,7 @@ Q_OBJECT
public:
/** Constructs an empty session group. */
SessionGroup();
SessionGroup(QObject* parent);
/** Destroys the session group and removes all connections between master and slave sessions. */
~SessionGroup();
......@@ -617,6 +617,9 @@ public:
*/
int masterMode() const;
private slots:
void sessionFinished();
private:
void connectPair(Session* master , Session* other);
void disconnectPair(Session* master , Session* other);
......
......@@ -43,6 +43,7 @@
// Konsole
#include "EditProfileDialog.h"
#include "CopyInputDialog.h"
#include "Emulation.h"
#include "Filter.h"
#include "History.h"
......@@ -73,6 +74,7 @@ SessionController::SessionController(Session* session , TerminalDisplay* view, Q
, KXMLGUIClient()
, _session(session)
, _view(view)
, _copyToGroup(0)
, _profileList(0)
, _previousState(-1)
, _viewUrlFilter(0)
......@@ -479,14 +481,10 @@ void SessionController::setupActions()
action->setShortcut( QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_S) );
connect( action , SIGNAL(triggered()) , this , SLOT(renameSession()) );
// Send to All
//TODO - Complete the implementation of 'Send Input to All' for
// a future KDE 4 release
//
//toggleAction = new KToggleAction(i18n("Send Input to All"),this);
//action = collection->addAction("send-input-to-all",toggleAction);
//connect( action , SIGNAL(toggled(bool)) , this , SIGNAL(sendInputToAll(bool)) );
// Copy Input To
action = collection->addAction("copy-input-to");
action->setText(i18n("Copy Input To..."));
connect( action , SIGNAL(triggered()) , this , SLOT(copyInputTo()) );
// Clear and Clear+Reset
action = collection->addAction("clear");
......@@ -657,12 +655,16 @@ void SessionController::editCurrentProfile()
}
void SessionController::renameSession()
{
QPointer<Session> guard(_session);
bool ok = false;
const QString& text = KInputDialog::getText( i18n("Rename Tab") ,
i18n("Enter new tab text:") ,
_session->tabTitleFormat(Session::LocalTabTitle) ,
&ok, QApplication::activeWindow() );
if ( ok )
if (!guard)
return;
if ( ok )
{
// renaming changes both the local and remote tab title formats, to save confusion over
// the tab title not changing if renaming the tab whilst the remote tab title format is
......@@ -711,6 +713,46 @@ void SessionController::pasteSelection()
{
_view->pasteSelection();
}
void SessionController::copyInputTo()
{
if (!_copyToGroup)
{
_copyToGroup = new SessionGroup(this);
_copyToGroup->addSession(_session);
_copyToGroup->setMasterStatus(_session,true);
_copyToGroup->setMasterMode(SessionGroup::CopyInputToAll);
}
CopyInputDialog* dialog = new CopyInputDialog(_view);
dialog->setMasterSession(_session);
QSet<Session*> currentGroup = QSet<Session*>::fromList(_copyToGroup->sessions());
currentGroup.remove(_session);
dialog->setChosenSessions(currentGroup);
QPointer<Session> guard(_session);
int result = dialog->exec();
if (!guard)
return;
if (result)
{
QSet<Session*> newGroup = dialog->chosenSessions();
newGroup.remove(_session);
QSet<Session*> completeGroup = newGroup | currentGroup;
foreach(Session* session, completeGroup)
{
if (newGroup.contains(session) && !currentGroup.contains(session))
_copyToGroup->addSession(session);
else if (!newGroup.contains(session) && currentGroup.contains(session))
_copyToGroup->removeSession(session);
}
}
delete dialog;
}
void SessionController::clear()
{
Emulation* emulation = _session->emulation();
......
......@@ -54,6 +54,7 @@ namespace Konsole
{
class Session;
class SessionGroup;
class ScreenWindow;
class TerminalDisplay;
class IncrementalSearchBar;
......@@ -140,12 +141,6 @@ signals:
*/
void focused( SessionController* controller );
/**
* Emitted when the user enables the "Send Input to All" menu
* item associated with this session.
*/
void sendInputToAll(bool sendToAll);
public slots:
/**
* Issues a command to the session to navigate to the specified URL.
......@@ -166,6 +161,7 @@ private slots:
void pasteSelection(); // shortcut only
void clear();
void clearAndReset();
void copyInputTo();
void editCurrentProfile();
void changeCodec(QTextCodec* codec);
//void searchHistory();
......@@ -233,6 +229,7 @@ private: