You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1554 lines
50 KiB
1554 lines
50 KiB
/* |
|
Copyright 2006-2008 by Robert Knight <robertknight@gmail.com> |
|
Copyright 2009 by Thomas Dreibholz <dreibh@iem.uni-due.de> |
|
|
|
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 "SessionController.h" |
|
|
|
// Qt |
|
#include <QtGui/QApplication> |
|
#include <QMenu> |
|
|
|
// KDE |
|
#include <KAction> |
|
#include <KDebug> |
|
#include <KIcon> |
|
#include <KInputDialog> |
|
#include <KLocale> |
|
#include <KMenu> |
|
#include <KMessageBox> |
|
#include <KRun> |
|
#include <kshell.h> |
|
#include <KStandardDirs> |
|
#include <KToggleAction> |
|
#include <KUrl> |
|
#include <KXmlGuiWindow> |
|
#include <KXMLGUIFactory> |
|
#include <KXMLGUIBuilder> |
|
#include <kdebug.h> |
|
#include <kcodecaction.h> |
|
#include <kdeversion.h> |
|
|
|
// Konsole |
|
#include "EditProfileDialog.h" |
|
#include "CopyInputDialog.h" |
|
#include "Emulation.h" |
|
#include "Filter.h" |
|
#include "History.h" |
|
#include "IncrementalSearchBar.h" |
|
#include "ScreenWindow.h" |
|
#include "Session.h" |
|
#include "ProfileList.h" |
|
#include "TerminalDisplay.h" |
|
#include "SessionManager.h" |
|
|
|
// for SaveHistoryTask |
|
#include <KFileDialog> |
|
#include <KIO/Job> |
|
#include <KJob> |
|
#include "TerminalCharacterDecoder.h" |
|
|
|
|
|
using namespace Konsole; |
|
|
|
KIcon SessionController::_activityIcon; |
|
KIcon SessionController::_silenceIcon; |
|
QSet<SessionController*> SessionController::_allControllers; |
|
QPointer<SearchHistoryThread> SearchHistoryTask::_thread; |
|
int SessionController::_lastControllerId; |
|
|
|
SessionController::SessionController(Session* session , TerminalDisplay* view, QObject* parent) |
|
: ViewProperties(parent) |
|
, KXMLGUIClient() |
|
, _session(session) |
|
, _view(view) |
|
, _copyToGroup(0) |
|
, _profileList(0) |
|
, _previousState(-1) |
|
, _viewUrlFilter(0) |
|
, _searchFilter(0) |
|
, _searchToggleAction(0) |
|
, _findNextAction(0) |
|
, _findPreviousAction(0) |
|
, _urlFilterUpdateRequired(false) |
|
, _codecAction(0) |
|
, _changeProfileMenu(0) |
|
, _listenForScreenWindowUpdates(false) |
|
, _preventClose(false) |
|
{ |
|
_allControllers.insert(this); |
|
|
|
Q_ASSERT( session ); |
|
Q_ASSERT( view ); |
|
|
|
// handle user interface related to session (menus etc.) |
|
if (isKonsolePart()) |
|
setXMLFile("konsole/partui.rc"); |
|
else |
|
setXMLFile("konsole/sessionui.rc"); |
|
|
|
setupActions(); |
|
actionCollection()->addAssociatedWidget(view); |
|
foreach (QAction* action, actionCollection()->actions()) |
|
action->setShortcutContext(Qt::WidgetWithChildrenShortcut); |
|
|
|
setIdentifier(++_lastControllerId); |
|
sessionTitleChanged(); |
|
|
|
view->installEventFilter(this); |
|
|
|
// listen for session resize requests |
|
connect( _session , SIGNAL(resizeRequest(const QSize&)) , this , |
|
SLOT(sessionResizeRequest(const QSize&)) ); |
|
|
|
// listen for popup menu requests |
|
connect( _view , SIGNAL(configureRequest(QPoint)) , this, |
|
SLOT(showDisplayContextMenu(QPoint)) ); |
|
|
|
// move view to newest output when keystrokes occur |
|
connect( _view , SIGNAL(keyPressedSignal(QKeyEvent*)) , this , |
|
SLOT(trackOutput(QKeyEvent*)) ); |
|
|
|
// listen to activity / silence notifications from session |
|
connect( _session , SIGNAL(stateChanged(int)) , this , |
|
SLOT(sessionStateChanged(int) )); |
|
// listen to title and icon changes |
|
connect( _session , SIGNAL(titleChanged()) , this , SLOT(sessionTitleChanged()) ); |
|
|
|
// listen for color changes |
|
connect( _session , SIGNAL(changeBackgroundColorRequest(QColor)) , _view , SLOT(setBackgroundColor(QColor)) ); |
|
connect( _session , SIGNAL(changeForegroundColorRequest(QColor)) , _view , SLOT(setForegroundColor(QColor)) ); |
|
|
|
// update the title when the session starts |
|
connect( _session , SIGNAL(started()) , this , SLOT(snapshot()) ); |
|
|
|
// listen for output changes to set activity flag |
|
connect( _session->emulation() , SIGNAL(outputChanged()) , this , |
|
SLOT(fireActivity()) ); |
|
|
|
// listen for detection of ZModem transfer |
|
connect( _session , SIGNAL(zmodemDetected()) , this , SLOT(zmodemDownload()) ); |
|
|
|
// listen for flow control status changes |
|
connect( _session , SIGNAL(flowControlEnabledChanged(bool)) , _view , |
|
SLOT(setFlowControlWarningEnabled(bool)) ); |
|
_view->setFlowControlWarningEnabled(_session->flowControlEnabled()); |
|
|
|
// take a snapshot of the session state every so often when |
|
// user activity occurs |
|
// |
|
// the timer is owned by the session so that it will be destroyed along |
|
// with the session |
|
QTimer* activityTimer = new QTimer(_session); |
|
activityTimer->setSingleShot(true); |
|
activityTimer->setInterval(2000); |
|
connect( _view , SIGNAL(keyPressedSignal(QKeyEvent*)) , activityTimer , SLOT(start()) ); |
|
connect( activityTimer , SIGNAL(timeout()) , this , SLOT(snapshot()) ); |
|
} |
|
|
|
void SessionController::updateSearchFilter() |
|
{ |
|
if ( _searchFilter ) |
|
{ |
|
Q_ASSERT( searchBar() && searchBar()->isVisible() ); |
|
|
|
_view->processFilters(); |
|
} |
|
} |
|
|
|
SessionController::~SessionController() |
|
{ |
|
if ( _view ) |
|
_view->setScreenWindow(0); |
|
|
|
_allControllers.remove(this); |
|
} |
|
void SessionController::trackOutput(QKeyEvent* event) |
|
{ |
|
Q_ASSERT( _view->screenWindow() ); |
|
|
|
// jump to the end of the history buffer unless the key pressed |
|
// is one of the three main modifiers, as these are used to select |
|
// the selection mode (eg. Ctrl+Alt+<Left Click> for column/block selection) |
|
switch (event->key()) |
|
{ |
|
case Qt::Key_Shift: |
|
case Qt::Key_Control: |
|
case Qt::Key_Alt: |
|
break; |
|
default: |
|
_view->screenWindow()->setTrackOutput(true); |
|
} |
|
} |
|
void SessionController::requireUrlFilterUpdate() |
|
{ |
|
// this method is called every time the screen window's output changes, so do not |
|
// do anything expensive here. |
|
|
|
_urlFilterUpdateRequired = true; |
|
} |
|
void SessionController::snapshot() |
|
{ |
|
Q_ASSERT( _session != 0 ); |
|
|
|
QString title = _session->getDynamicTitle(); |
|
title = title.simplified(); |
|
|
|
// Visualize that the session is broadcasting to others |
|
if (_copyToGroup && _copyToGroup->sessions().count() > 1) { |
|
title.append('*'); |
|
} |
|
updateSessionIcon(); |
|
|
|
// apply new title |
|
if ( !title.isEmpty() ) |
|
_session->setTitle(Session::DisplayedTitleRole,title); |
|
else |
|
_session->setTitle(Session::DisplayedTitleRole,_session->title(Session::NameRole)); |
|
} |
|
|
|
QString SessionController::currentDir() const |
|
{ |
|
return _session->currentWorkingDirectory(); |
|
} |
|
|
|
KUrl SessionController::url() const |
|
{ |
|
return _session->getUrl(); |
|
} |
|
|
|
void SessionController::rename() |
|
{ |
|
renameSession(); |
|
} |
|
|
|
void SessionController::openUrl( const KUrl& url ) |
|
{ |
|
// handle local paths |
|
if ( url.isLocalFile() ) |
|
{ |
|
QString path = url.toLocalFile(); |
|
_session->emulation()->sendText("cd " + KShell::quoteArg(path) + '\r'); |
|
} |
|
else if ( url.protocol() == "ssh" ) |
|
{ |
|
_session->emulation()->sendText("ssh "); |
|
|
|
if ( url.port() > -1 ) |
|
_session->emulation()->sendText("-p " + QString::number(url.port()) + ' ' ); |
|
if ( url.hasUser() ) |
|
_session->emulation()->sendText(url.user() + '@'); |
|
if ( url.hasHost() ) |
|
_session->emulation()->sendText(url.host() + '\r'); |
|
} |
|
else if ( url.protocol() == "telnet" ) |
|
{ |
|
_session->emulation()->sendText("telnet "); |
|
|
|
if ( url.hasUser() ) |
|
_session->emulation()->sendText("-l " + url.user() + ' '); |
|
if ( url.hasHost() ) |
|
_session->emulation()->sendText(url.host() + ' '); |
|
if ( url.port() > -1 ) |
|
_session->emulation()->sendText(QString::number(url.port())); |
|
_session->emulation()->sendText("\r"); |
|
} |
|
else |
|
{ |
|
//TODO Implement handling for other Url types |
|
|
|
KMessageBox::sorry(_view->window(), |
|
i18n("Konsole does not know how to open the bookmark: ") + |
|
url.prettyUrl()); |
|
|
|
kWarning(1211) << "Unable to open bookmark at url" << url << ", I do not know" |
|
<< " how to handle the protocol " << url.protocol(); |
|
} |
|
} |
|
|
|
bool SessionController::eventFilter(QObject* watched , QEvent* event) |
|
{ |
|
if ( watched == _view ) |
|
{ |
|
if ( event->type() == QEvent::FocusIn ) |
|
{ |
|
// notify the world that the view associated with this session has been focused |
|
// used by the view manager to update the title of the MainWindow widget containing the view |
|
emit focused(this); |
|
|
|
// when the view is focused, set bell events from the associated session to be delivered |
|
// by the focused view |
|
|
|
// first, disconnect any other views which are listening for bell signals from the session |
|
disconnect( _session , SIGNAL(bellRequest(const QString&)) , 0 , 0 ); |
|
// second, connect the newly focused view to listen for the session's bell signal |
|
connect( _session , SIGNAL(bellRequest(const QString&)) , |
|
_view , SLOT(bell(const QString&)) ); |
|
|
|
if(_copyToAllTabsAction->isChecked()) { |
|
// A session with "Copy To All Tabs" has come into focus: |
|
// Ensure that newly created sessions are included in _copyToGroup! |
|
copyInputToAllTabs(); |
|
} |
|
} |
|
// when a mouse move is received, create the URL filter and listen for output changes if |
|
// it has not already been created. If it already exists, then update only if the output |
|
// has changed since the last update ( _urlFilterUpdateRequired == true ) |
|
// |
|
// also check that no mouse buttons are pressed since the URL filter only applies when |
|
// the mouse is hovering over the view |
|
if ( event->type() == QEvent::MouseMove && |
|
(!_viewUrlFilter || _urlFilterUpdateRequired) && |
|
((QMouseEvent*)event)->buttons() == Qt::NoButton ) |
|
{ |
|
if ( _view->screenWindow() && !_viewUrlFilter ) |
|
{ |
|
connect( _view->screenWindow() , SIGNAL(scrolled(int)) , this , |
|
SLOT(requireUrlFilterUpdate()) ); |
|
connect( _view->screenWindow() , SIGNAL(outputChanged()) , this , |
|
SLOT(requireUrlFilterUpdate()) ); |
|
|
|
// install filter on the view to highlight URLs |
|
_viewUrlFilter = new UrlFilter(); |
|
_view->filterChain()->addFilter( _viewUrlFilter ); |
|
} |
|
|
|
_view->processFilters(); |
|
_urlFilterUpdateRequired = false; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void SessionController::removeSearchFilter() |
|
{ |
|
if (!_searchFilter) |
|
return; |
|
|
|
_view->filterChain()->removeFilter(_searchFilter); |
|
delete _searchFilter; |
|
_searchFilter = 0; |
|
} |
|
|
|
void SessionController::setSearchBar(IncrementalSearchBar* searchBar) |
|
{ |
|
// disconnect the existing search bar |
|
if ( _searchBar ) |
|
{ |
|
disconnect( this , 0 , _searchBar , 0 ); |
|
disconnect( _searchBar , 0 , this , 0 ); |
|
} |
|
|
|
// remove any existing search filter |
|
removeSearchFilter(); |
|
|
|
// connect new search bar |
|
_searchBar = searchBar; |
|
if ( _searchBar ) |
|
{ |
|
connect( _searchBar , SIGNAL(closeClicked()) , this , SLOT(searchClosed()) ); |
|
connect( _searchBar , SIGNAL(findNextClicked()) , this , SLOT(findNextInHistory()) ); |
|
connect( _searchBar , SIGNAL(findPreviousClicked()) , this , SLOT(findPreviousInHistory()) ); |
|
connect( _searchBar , SIGNAL(highlightMatchesToggled(bool)) , this , SLOT(highlightMatches(bool)) ); |
|
|
|
// if the search bar was previously active |
|
// then re-enter search mode |
|
searchHistory( _searchToggleAction->isChecked() ); |
|
} |
|
} |
|
IncrementalSearchBar* SessionController::searchBar() const |
|
{ |
|
return _searchBar; |
|
} |
|
|
|
void SessionController::setShowMenuAction(QAction* action) |
|
{ |
|
actionCollection()->addAction("show-menubar",action); |
|
} |
|
|
|
void SessionController::setupActions() |
|
{ |
|
KAction* action = 0; |
|
KToggleAction* toggleAction = 0; |
|
KActionCollection* collection = actionCollection(); |
|
|
|
// Close Session |
|
action = collection->addAction("close-session", this, SLOT(closeSession())); |
|
action->setIcon(KIcon("tab-close")); |
|
action->setText(i18n("&Close Tab")); |
|
action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_W)); |
|
|
|
// Open Browser |
|
action = collection->addAction("open-browser", this, SLOT(openBrowser())); |
|
action->setText(i18n("Open File Manager")); |
|
action->setIcon(KIcon("system-file-manager")); |
|
|
|
// Copy and Paste |
|
action = KStandardAction::copy(this, SLOT(copy()), collection); |
|
action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_C)); |
|
|
|
action = KStandardAction::paste(this, SLOT(paste()), collection); |
|
KShortcut pasteShortcut = action->shortcut(); |
|
pasteShortcut.setPrimary(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_V)); |
|
pasteShortcut.setAlternate(QKeySequence(Qt::SHIFT + Qt::Key_Insert)); |
|
action->setShortcut(pasteShortcut); |
|
|
|
action = collection->addAction("paste-selection", this, SLOT(pasteSelection())); |
|
action->setText(i18n("Paste Selection")); |
|
action->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Insert)); |
|
|
|
// Rename Session |
|
action = collection->addAction("rename-session", this, SLOT(renameSession())); |
|
action->setText( i18n("&Rename Tab...") ); |
|
action->setShortcut( QKeySequence(Qt::CTRL+Qt::ALT+Qt::Key_S) ); |
|
|
|
// Copy Input To -> All Tabs in Current Window |
|
_copyToAllTabsAction = collection->addAction("copy-input-to-all-tabs", this, SLOT(copyInputToAllTabs())); |
|
_copyToAllTabsAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Comma)); |
|
_copyToAllTabsAction->setText(i18n("&All Tabs in Current Window")); |
|
_copyToAllTabsAction->setCheckable(true); |
|
|
|
// Copy Input To -> Select Tabs |
|
_copyToSelectedAction = collection->addAction("copy-input-to-selected-tabs", this, SLOT(copyInputToSelectedTabs())); |
|
_copyToSelectedAction->setShortcut( QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Period) ); |
|
_copyToSelectedAction->setText(i18n("&Select Tabs...")); |
|
_copyToSelectedAction->setCheckable(true); |
|
|
|
// Copy Input To -> None |
|
_copyToNoneAction = collection->addAction("copy-input-to-none", this, SLOT(copyInputToNone())); |
|
_copyToNoneAction->setText(i18n("&None")); |
|
_copyToNoneAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_Slash)); |
|
_copyToNoneAction->setCheckable(true); |
|
_copyToNoneAction->setChecked(true); |
|
|
|
action = collection->addAction("zmodem-upload", this, SLOT(zmodemUpload())); |
|
action->setText(i18n("&ZModem Upload...")); |
|
action->setIcon(KIcon("document-open")); |
|
action->setShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_U)); |
|
|
|
// Monitor |
|
toggleAction = new KToggleAction(i18n("Monitor for &Activity"),this); |
|
toggleAction->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_A)); |
|
action = collection->addAction("monitor-activity", toggleAction); |
|
connect(action, SIGNAL(toggled(bool)), this, SLOT(monitorActivity(bool))); |
|
|
|
toggleAction = new KToggleAction(i18n("Monitor for &Silence"),this); |
|
toggleAction->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_I)); |
|
action = collection->addAction("monitor-silence", toggleAction); |
|
connect(action, SIGNAL(toggled(bool)), this, SLOT(monitorSilence(bool))); |
|
|
|
// Character Encoding |
|
_codecAction = new KCodecAction(i18n("Set &Encoding"),this); |
|
_codecAction->setIcon(KIcon("character-set")); |
|
collection->addAction("set-encoding", _codecAction); |
|
connect(_codecAction->menu(), SIGNAL(aboutToShow()), this, SLOT(updateCodecAction())); |
|
connect(_codecAction, SIGNAL(triggered(QTextCodec*)), this, SLOT(changeCodec(QTextCodec*))); |
|
|
|
// Text Size |
|
action = collection->addAction("enlarge-font", this, SLOT(increaseTextSize())); |
|
action->setText(i18n("Enlarge Font")); |
|
action->setIcon(KIcon("format-font-size-more")); |
|
action->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Plus)); |
|
|
|
action = collection->addAction("shrink-font", this, SLOT(decreaseTextSize())); |
|
action->setText(i18n("Shrink Font")); |
|
action->setIcon(KIcon("format-font-size-less")); |
|
action->setShortcut(QKeySequence(Qt::CTRL+Qt::SHIFT+Qt::Key_Minus)); |
|
|
|
// History |
|
_searchToggleAction = KStandardAction::find(this, NULL, collection); |
|
_searchToggleAction->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_F)); |
|
_searchToggleAction->setCheckable(true); |
|
connect(_searchToggleAction, SIGNAL(toggled(bool)), this, SLOT(searchHistory(bool))); |
|
|
|
_findNextAction = KStandardAction::findNext(this, SLOT(findNextInHistory()), collection); |
|
_findNextAction->setEnabled(false); |
|
|
|
_findPreviousAction = KStandardAction::findPrev(this, SLOT(findPreviousInHistory()), collection); |
|
_findPreviousAction->setShortcut(QKeySequence(Qt::SHIFT + Qt::Key_F3)); |
|
_findPreviousAction->setEnabled(false); |
|
|
|
action = KStandardAction::saveAs(this, SLOT(saveHistory()), collection); |
|
action->setText(i18n("Save Output &As...")); |
|
|
|
action = collection->addAction("configure-history", this, SLOT(showHistoryOptions())); |
|
action->setText(i18n("Configure Scrollback...")); |
|
action->setIcon(KIcon("configure")); |
|
|
|
action = collection->addAction("clear-history", this, SLOT(clearHistory())); |
|
action->setText(i18n("Clear Scrollback")); |
|
action->setIcon(KIcon("edit-clear-history")); |
|
|
|
action = collection->addAction("clear-history-and-reset", this, SLOT(clearHistoryAndReset())); |
|
action->setText(i18n("Clear Scrollback and Reset")); |
|
action->setIcon(KIcon("edit-clear-history")); |
|
action->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_X)); |
|
|
|
// Profile Options |
|
action = collection->addAction("edit-current-profile", this, SLOT(editCurrentProfile())); |
|
action->setText(i18n("Configure Current Profile...")); |
|
action->setIcon(KIcon("document-properties") ); |
|
|
|
_changeProfileMenu = new KActionMenu(i18n("Change Profile"), _view); |
|
collection->addAction("change-profile", _changeProfileMenu); |
|
connect(_changeProfileMenu->menu(), SIGNAL(aboutToShow()), this, SLOT(prepareChangeProfileMenu())); |
|
} |
|
|
|
void SessionController::changeProfile(Profile::Ptr profile) |
|
{ |
|
SessionManager::instance()->setSessionProfile(_session,profile); |
|
} |
|
|
|
void SessionController::prepareChangeProfileMenu() |
|
{ |
|
if (_changeProfileMenu->menu()->isEmpty()) { |
|
_profileList = new ProfileList(false,this); |
|
connect(_profileList, SIGNAL(profileSelected(Profile::Ptr)), this, SLOT(changeProfile(Profile::Ptr))); |
|
} |
|
|
|
_changeProfileMenu->menu()->clear(); |
|
_changeProfileMenu->menu()->addActions(_profileList->actions()); |
|
} |
|
void SessionController::updateCodecAction() |
|
{ |
|
_codecAction->setCurrentCodec(QString(_session->emulation()->codec()->name())); |
|
} |
|
|
|
void SessionController::changeCodec(QTextCodec* codec) |
|
{ |
|
_session->setCodec(codec); |
|
} |
|
|
|
void SessionController::editCurrentProfile() |
|
{ |
|
EditProfileDialog* dialog = new EditProfileDialog( QApplication::activeWindow() ); |
|
|
|
dialog->setProfile(SessionManager::instance()->sessionProfile(_session)); |
|
dialog->show(); |
|
} |
|
|
|
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 (!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 |
|
// being displayed |
|
// |
|
// The downside of this approach is that after renaming a tab manually, the ability to |
|
// have separate formats for local and remote activities is lost |
|
_session->setTabTitleFormat(Session::LocalTabTitle,text); |
|
_session->setTabTitleFormat(Session::RemoteTabTitle,text); |
|
|
|
// trigger an update of the tab text |
|
snapshot(); |
|
} |
|
} |
|
void SessionController::saveSession() |
|
{ |
|
Q_ASSERT(0); // not implemented yet |
|
|
|
//SaveSessionDialog dialog(_view); |
|
//int result = dialog.exec(); |
|
} |
|
bool SessionController::confirmClose() const |
|
{ |
|
if (_session->isForegroundProcessActive()) |
|
{ |
|
QString title = _session->foregroundProcessName(); |
|
|
|
// hard coded for now. In future make it possible for the user to specify which programs |
|
// are ignored when considering whether to display a confirmation |
|
QStringList ignoreList; |
|
ignoreList << QString(qgetenv("SHELL")).section('/',-1); |
|
if (ignoreList.contains(title)) |
|
return true; |
|
|
|
QString question; |
|
if (title.isEmpty()) |
|
question = i18n("A program is currently running in this session." |
|
" Are you sure you want to close it?"); |
|
else |
|
question = i18n("The program '%1' is currently running in this session." |
|
" Are you sure you want to close it?",title); |
|
|
|
int result = KMessageBox::warningYesNo(_view->window(),question,i18n("Confirm Close")); |
|
return (result == KMessageBox::Yes) ? true : false; |
|
} |
|
return true; |
|
} |
|
void SessionController::closeSession() |
|
{ |
|
if (_preventClose) |
|
return; |
|
|
|
if (confirmClose()) |
|
_session->close(); |
|
} |
|
|
|
void SessionController::openBrowser() |
|
{ |
|
new KRun(url(), QApplication::activeWindow()); |
|
} |
|
|
|
void SessionController::copy() |
|
{ |
|
_view->copyClipboard(); |
|
} |
|
|
|
void SessionController::paste() |
|
{ |
|
_view->pasteClipboard(); |
|
} |
|
void SessionController::pasteSelection() |
|
{ |
|
_view->pasteSelection(); |
|
} |
|
static const KXmlGuiWindow* findWindow(const QObject* object) |
|
{ |
|
// Walk up the QObject hierarchy to find a KXmlGuiWindow. |
|
while(object != NULL) { |
|
const KXmlGuiWindow* window = dynamic_cast<const KXmlGuiWindow*>(object); |
|
if(window != NULL) { |
|
return(window); |
|
} |
|
object = object->parent(); |
|
} |
|
return(NULL); |
|
} |
|
|
|
static bool hasTerminalDisplayInSameWindow(const Session* session, const KXmlGuiWindow* window) |
|
{ |
|
// Iterate all TerminalDisplays of this Session ... |
|
QListIterator<TerminalDisplay*> terminalDisplayIterator(session->views()); |
|
while(terminalDisplayIterator.hasNext()) { |
|
const TerminalDisplay* terminalDisplay = terminalDisplayIterator.next(); |
|
// ... and check whether a TerminalDisplay has the same |
|
// window as given in the parameter |
|
if(window == findWindow(terminalDisplay)) { |
|
return(true); |
|
} |
|
} |
|
return(false); |
|
} |
|
|
|
void SessionController::copyInputToAllTabs() |
|
{ |
|
if(!_copyToGroup) { |
|
_copyToGroup = new SessionGroup(this); |
|
} |
|
|
|
// Find our window ... |
|
const KXmlGuiWindow* myWindow = findWindow(_view); |
|
|
|
QSet<Session*> group = |
|
QSet<Session*>::fromList(SessionManager::instance()->sessions()); |
|
for(QSet<Session*>::iterator iterator = group.begin(); |
|
iterator != group.end(); ++iterator) { |
|
Session* session = *iterator; |
|
|
|
// First, ensure that the session is removed |
|
// (necessary to avoid duplicates on addSession()!) |
|
_copyToGroup->removeSession(session); |
|
|
|
// Add current session if it is displayed our window |
|
if(hasTerminalDisplayInSameWindow(session, myWindow)) { |
|
_copyToGroup->addSession(session); |
|
} |
|
} |
|
_copyToGroup->setMasterStatus(_session, true); |
|
_copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); |
|
|
|
snapshot(); |
|
_copyToAllTabsAction->setChecked(true); |
|
_copyToSelectedAction->setChecked(false); |
|
_copyToNoneAction->setChecked(false); |
|
} |
|
|
|
void SessionController::copyInputToSelectedTabs() |
|
{ |
|
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); |
|
} |
|
|
|
_copyToGroup->setMasterStatus(_session, true); |
|
_copyToGroup->setMasterMode(SessionGroup::CopyInputToAll); |
|
snapshot(); |
|
} |
|
|
|
delete dialog; |
|
_copyToAllTabsAction->setChecked(false); |
|
_copyToSelectedAction->setChecked(true); |
|
_copyToNoneAction->setChecked(false); |
|
} |
|
|
|
void SessionController::copyInputToNone() |
|
{ |
|
if (!_copyToGroup) // No 'Copy To' is active |
|
return; |
|
|
|
QSet<Session*> group = |
|
QSet<Session*>::fromList(SessionManager::instance()->sessions()); |
|
for(QSet<Session*>::iterator iterator = group.begin(); |
|
iterator != group.end(); ++iterator) { |
|
Session* session = *iterator; |
|
|
|
if(session != _session) { |
|
_copyToGroup->removeSession(*iterator); |
|
} |
|
} |
|
delete _copyToGroup; |
|
_copyToGroup = NULL; |
|
snapshot(); |
|
|
|
_copyToAllTabsAction->setChecked(false); |
|
_copyToSelectedAction->setChecked(false); |
|
_copyToNoneAction->setChecked(true); |
|
} |
|
|
|
void SessionController::searchClosed() |
|
{ |
|
_searchToggleAction->toggle(); |
|
} |
|
|
|
#if 0 |
|
void SessionController::searchHistory() |
|
{ |
|
searchHistory(true); |
|
} |
|
#endif |
|
|
|
void SessionController::listenForScreenWindowUpdates() |
|
{ |
|
if (_listenForScreenWindowUpdates) |
|
return; |
|
|
|
connect( _view->screenWindow() , SIGNAL(outputChanged()) , this , |
|
SLOT(updateSearchFilter()) ); |
|
connect( _view->screenWindow() , SIGNAL(scrolled(int)) , this , |
|
SLOT(updateSearchFilter()) ); |
|
|
|
_listenForScreenWindowUpdates = true; |
|
} |
|
|
|
// searchHistory() may be called either as a result of clicking a menu item or |
|
// as a result of changing the search bar widget |
|
void SessionController::searchHistory(bool showSearchBar) |
|
{ |
|
if ( _searchBar ) |
|
{ |
|
_searchBar->setVisible(showSearchBar); |
|
|
|
if (showSearchBar) |
|
{ |
|
removeSearchFilter(); |
|
|
|
listenForScreenWindowUpdates(); |
|
|
|
_searchFilter = new RegExpFilter(); |
|
_view->filterChain()->addFilter(_searchFilter); |
|
connect( _searchBar , SIGNAL(searchChanged(const QString&)) , this , |
|
SLOT(searchTextChanged(const QString&)) ); |
|
|
|
// invoke search for matches for the current search text |
|
const QString& currentSearchText = _searchBar->searchText(); |
|
if (!currentSearchText.isEmpty()) |
|
{ |
|
searchTextChanged(currentSearchText); |
|
} |
|
|
|
setFindNextPrevEnabled(true); |
|
} |
|
else |
|
{ |
|
setFindNextPrevEnabled(false); |
|
|
|
disconnect( _searchBar , SIGNAL(searchChanged(const QString&)) , this , |
|
SLOT(searchTextChanged(const QString&)) ); |
|
|
|
removeSearchFilter(); |
|
|
|
_view->setFocus( Qt::ActiveWindowFocusReason ); |
|
} |
|
} |
|
} |
|
void SessionController::setFindNextPrevEnabled(bool enabled) |
|
{ |
|
_findNextAction->setEnabled(enabled); |
|
_findPreviousAction->setEnabled(enabled); |
|
} |
|
void SessionController::searchTextChanged(const QString& text) |
|
{ |
|
Q_ASSERT( _view->screenWindow() ); |
|
|
|
if ( text.isEmpty() ) |
|
_view->screenWindow()->clearSelection(); |
|
|
|
// update search. this is called even when the text is |
|
// empty to clear the view's filters |
|
beginSearch(text , SearchHistoryTask::ForwardsSearch); |
|
} |
|
void SessionController::searchCompleted(bool success) |
|
{ |
|
if ( _searchBar ) |
|
_searchBar->setFoundMatch(success); |
|
} |
|
|
|
void SessionController::beginSearch(const QString& text , int direction) |
|
{ |
|
Q_ASSERT( _searchBar ); |
|
Q_ASSERT( _searchFilter ); |
|
|
|
Qt::CaseSensitivity caseHandling = _searchBar->matchCase() ? Qt::CaseSensitive : Qt::CaseInsensitive; |
|
QRegExp::PatternSyntax syntax = _searchBar->matchRegExp() ? QRegExp::RegExp : QRegExp::FixedString; |
|
|
|
QRegExp regExp( text.trimmed() , caseHandling , syntax ); |
|
_searchFilter->setRegExp(regExp); |
|
|
|
if ( !regExp.isEmpty() ) |
|
{ |
|
SearchHistoryTask* task = new SearchHistoryTask(this); |
|
|
|
connect( task , SIGNAL(completed(bool)) , this , SLOT(searchCompleted(bool)) ); |
|
|
|
task->setRegExp(regExp); |
|
task->setSearchDirection( (SearchHistoryTask::SearchDirection)direction ); |
|
task->setAutoDelete(true); |
|
task->addScreenWindow( _session , _view->screenWindow() ); |
|
task->execute(); |
|
} |
|
|
|
_view->processFilters(); |
|
} |
|
void SessionController::highlightMatches(bool highlight) |
|
{ |
|
if ( highlight ) |
|
{ |
|
_view->filterChain()->addFilter(_searchFilter); |
|
_view->processFilters(); |
|
} |
|
else |
|
{ |
|
_view->filterChain()->removeFilter(_searchFilter); |
|
} |
|
|
|
_view->update(); |
|
} |
|
void SessionController::findNextInHistory() |
|
{ |
|
Q_ASSERT( _searchBar ); |
|
Q_ASSERT( _searchFilter ); |
|
|
|
beginSearch(_searchBar->searchText(),SearchHistoryTask::ForwardsSearch); |
|
} |
|
void SessionController::findPreviousInHistory() |
|
{ |
|
Q_ASSERT( _searchBar ); |
|
Q_ASSERT( _searchFilter ); |
|
|
|
beginSearch(_searchBar->searchText(),SearchHistoryTask::BackwardsSearch); |
|
} |
|
void SessionController::showHistoryOptions() |
|
{ |
|
HistorySizeDialog* dialog = new HistorySizeDialog( QApplication::activeWindow() ); |
|
const HistoryType& currentHistory = _session->historyType(); |
|
|
|
if ( currentHistory.isEnabled() ) |
|
{ |
|
if ( currentHistory.isUnlimited() ) |
|
dialog->setMode( HistorySizeDialog::UnlimitedHistory ); |
|
else |
|
{ |
|
dialog->setMode( HistorySizeDialog::FixedSizeHistory ); |
|
dialog->setLineCount( currentHistory.maximumLineCount() ); |
|
} |
|
} |
|
else |
|
dialog->setMode( HistorySizeDialog::NoHistory ); |
|
|
|
connect( dialog , SIGNAL(optionsChanged(int,int,bool)) , |
|
this , SLOT(scrollBackOptionsChanged(int,int,bool)) ); |
|
|
|
dialog->show(); |
|
} |
|
void SessionController::sessionResizeRequest(const QSize& size) |
|
{ |
|
//kDebug(1211) << "View resize requested to " << size; |
|
_view->setSize(size.width(),size.height()); |
|
} |
|
void SessionController::scrollBackOptionsChanged(int mode, int lines, bool saveToCurrentProfile ) |
|
{ |
|
switch (mode) |
|
{ |
|
case HistorySizeDialog::NoHistory: |
|
_session->setHistoryType( HistoryTypeNone() ); |
|
break; |
|
case HistorySizeDialog::FixedSizeHistory: |
|
_session->setHistoryType( CompactHistoryType(lines) ); |
|
break; |
|
case HistorySizeDialog::UnlimitedHistory: |
|
_session->setHistoryType( HistoryTypeFile() ); |
|
break; |
|
} |
|
if (saveToCurrentProfile) |
|
{ |
|
Profile::Ptr profile = SessionManager::instance()->sessionProfile(_session); |
|
|
|
switch (mode) |
|
{ |
|
case HistorySizeDialog::NoHistory: |
|
profile->setProperty(Profile::HistoryMode , Profile::DisableHistory); |
|
break; |
|
case HistorySizeDialog::FixedSizeHistory: |
|
profile->setProperty(Profile::HistoryMode , Profile::FixedSizeHistory); |
|
profile->setProperty(Profile::HistorySize , lines); |
|
break; |
|
case HistorySizeDialog::UnlimitedHistory: |
|
profile->setProperty(Profile::HistoryMode , Profile::UnlimitedHistory); |
|
break; |
|
} |
|
SessionManager::instance()->changeProfile(profile, profile->setProperties()); |
|
} |
|
} |
|
|
|
void SessionController::saveHistory() |
|
{ |
|
SessionTask* task = new SaveHistoryTask(this); |
|
task->setAutoDelete(true); |
|
task->addSession( _session ); |
|
task->execute(); |
|
} |
|
|
|
void SessionController::clearHistory() |
|
{ |
|
_session->clearHistory(); |
|
_view->updateImage(); // To reset view scrollbar |
|
} |
|
|
|
void SessionController::clearHistoryAndReset() |
|
{ |
|
Emulation* emulation = _session->emulation(); |
|
emulation->reset(); |
|
//_session->refresh(); |
|
clearHistory(); |
|
} |
|
|
|
void SessionController::increaseTextSize() |
|
{ |
|
QFont font = _view->getVTFont(); |
|
font.setPointSizeF(font.pointSizeF()+1); |
|
_view->setVTFont(font); |
|
|
|
//TODO - Save this setting as a session default |
|
} |
|
|
|
void SessionController::decreaseTextSize() |
|
{ |
|
static const qreal MinimumFontSize = 6; |
|
|
|
QFont font = _view->getVTFont(); |
|
font.setPointSizeF( qMax(font.pointSizeF()-1,MinimumFontSize) ); |
|
_view->setVTFont(font); |
|
|
|
//TODO - Save this setting as a session default |
|
} |
|
|
|
void SessionController::monitorActivity(bool monitor) |
|
{ |
|
_session->setMonitorActivity(monitor); |
|
} |
|
void SessionController::monitorSilence(bool monitor) |
|
{ |
|
_session->setMonitorSilence(monitor); |
|
} |
|
void SessionController::updateSessionIcon() |
|
{ |
|
// Visualize that the session is broadcasting to others |
|
if (_copyToGroup && _copyToGroup->sessions().count() > 1) { |
|
// Master Mode: set different icon, to warn the user to be careful |
|
setIcon(KIcon("emblem-important")); |
|
} |
|
else { |
|
// Not in Master Mode: use normal icon |
|
setIcon( _sessionIcon ); |
|
} |
|
} |
|
void SessionController::sessionTitleChanged() |
|
{ |
|
if ( _sessionIconName != _session->iconName() ) |
|
{ |
|
_sessionIconName = _session->iconName(); |
|
_sessionIcon = KIcon( _sessionIconName ); |
|
updateSessionIcon(); |
|
} |
|
|
|
QString title = _session->title(Session::DisplayedTitleRole); |
|
|
|
// special handling for the "%w" marker which is replaced with the |
|
// window title set by the shell |
|
title.replace("%w",_session->userTitle()); |
|
// special handling for the "%#" marker which is replaced with the |
|
// number of the shell |
|
title.replace("%#",QString::number(_session->sessionId())); |
|
|
|
if ( title.isEmpty() ) |
|
title = _session->title(Session::NameRole); |
|
|
|
setTitle( title ); |
|
} |
|
|
|
void SessionController::showDisplayContextMenu(const QPoint& position) |
|
{ |
|
// needed to make sure the popup menu is available, even if a hosting |
|
// application did not merge our GUI. |
|
if (!factory()) |
|
{ |
|
if (!clientBuilder()) |
|
setClientBuilder(new KXMLGUIBuilder(_view)); |
|
|
|
KXMLGUIFactory* factory = new KXMLGUIFactory(clientBuilder(), this); |
|
factory->addClient(this); |
|
} |
|
|
|
QMenu* popup = qobject_cast<QMenu*>(factory()->container("session-popup-menu",this)); |
|
if (popup) |
|
{ |
|
// prepend content-specific actions such as "Open Link", "Copy Email Address" etc. |
|
QList<QAction*> contentActions = _view->filterActions(position); |
|
QAction* contentSeparator = new QAction(popup); |
|
contentSeparator->setSeparator(true); |
|
contentActions << contentSeparator; |
|
|
|
_preventClose = true; |
|
|
|
popup->insertActions(popup->actions().value(0,0),contentActions); |
|
QAction* chosen = popup->exec( _view->mapToGlobal(position) ); |
|
|
|
// remove content-specific actions, unless the close action was chosen |
|
// in which case the popup menu will be partially destroyed at this point |
|
foreach(QAction* action,contentActions) |
|
popup->removeAction(action); |
|
delete contentSeparator; |
|
|
|
_preventClose = false; |
|
|
|
if (chosen && chosen->objectName() == "close-session") |
|
chosen->trigger(); |
|
} |
|
else |
|
{ |
|
kWarning() << "Unable to display popup menu for session" |
|
<< _session->title(Session::NameRole) |
|
<< ", no GUI factory available to build the popup."; |
|
} |
|
} |
|
|
|
void SessionController::sessionStateChanged(int state) |
|
{ |
|
if ( state == _previousState ) |
|
return; |
|
|
|
_previousState = state; |
|
|
|
// TODO - Replace the icon choices below when suitable icons for silence and activity |
|
// are available |
|
if ( state == NOTIFYACTIVITY ) |
|
{ |
|
if (_activityIcon.isNull()) |
|
{ |
|
_activityIcon = KIcon("dialog-information"); |
|
} |
|
|
|
setIcon(_activityIcon); |
|
} |
|
else if ( state == NOTIFYSILENCE ) |
|
{ |
|
if (_silenceIcon.isNull()) |
|
{ |
|
_silenceIcon = KIcon("dialog-information"); |
|
} |
|
|
|
setIcon(_silenceIcon); |
|
} |
|
else if ( state == NOTIFYNORMAL ) |
|
{ |
|
if ( _sessionIconName != _session->iconName() ) |
|
{ |
|
_sessionIconName = _session->iconName(); |
|
_sessionIcon = KIcon( _sessionIconName ); |
|
} |
|
|
|
updateSessionIcon(); |
|
} |
|
} |
|
|
|
void SessionController::zmodemDownload() |
|
{ |
|
QString zmodem = KGlobal::dirs()->findExe("rz"); |
|
if(zmodem.isEmpty()) { |
|
zmodem = KGlobal::dirs()->findExe("lrz"); |
|
} |
|
if(!zmodem.isEmpty()) { |
|
const QString path = KFileDialog::getExistingDirectory( |
|
QString(), _view, |
|
i18n("Save ZModem Download to...")); |
|
|
|
if(!path.isEmpty()) { |
|
_session->startZModem(zmodem, path, QStringList()); |
|
return; |
|
} |
|
} |
|
else { |
|
KMessageBox::error(_view, |
|
i18n("<p>A ZModem file transfer attempt has been detected, " |
|
"but no suitable ZModem software was found on this system.</p>" |
|
"<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>")); |
|
} |
|
_session->cancelZModem(); |
|
return; |
|
} |
|
|
|
void SessionController::zmodemUpload() |
|
{ |
|
if(_session->isZModemBusy()) { |
|
KMessageBox::sorry(_view, |
|
i18n("<p>The current session already has a ZModem file transfer in progress.</p>")); |
|
return; |
|
} |
|
QString zmodem = KGlobal::dirs()->findExe("sz"); |
|
if(zmodem.isEmpty()) { |
|
zmodem = KGlobal::dirs()->findExe("lsz"); |
|
} |
|
if(zmodem.isEmpty()) { |
|
KMessageBox::sorry(_view, |
|
i18n("<p>No suitable ZModem software was found on this system.</p>" |
|
"<p>You may wish to install the 'rzsz' or 'lrzsz' package.</p>")); |
|
return; |
|
} |
|
|
|
QStringList files = KFileDialog::getOpenFileNames(KUrl(), QString(), _view, |
|
i18n("Select Files for ZModem Upload")); |
|
if(!files.isEmpty()) { |
|
_session->startZModem(zmodem, QString(), files); |
|
} |
|
} |
|
|
|
bool SessionController::isKonsolePart() const |
|
{ |
|
// Check to see if we are being called from Konsole or a KPart |
|
if (QString(qApp->metaObject()->className()) == "Konsole::Application") |
|
return false; |
|
else |
|
return true; |
|
} |
|
|
|
SessionTask::SessionTask(QObject* parent) |
|
: QObject(parent) |
|
, _autoDelete(false) |
|
{ |
|
} |
|
void SessionTask::setAutoDelete(bool enable) |
|
{ |
|
_autoDelete = enable; |
|
} |
|
bool SessionTask::autoDelete() const |
|
{ |
|
return _autoDelete; |
|
} |
|
void SessionTask::addSession(Session* session) |
|
{ |
|
_sessions << session; |
|
} |
|
QList<SessionPtr> SessionTask::sessions() const |
|
{ |
|
return _sessions; |
|
} |
|
|
|
SaveHistoryTask::SaveHistoryTask(QObject* parent) |
|
: SessionTask(parent) |
|
{ |
|
} |
|
SaveHistoryTask::~SaveHistoryTask() |
|
{ |
|
} |
|
|
|
void SaveHistoryTask::execute() |
|
{ |
|
QListIterator<SessionPtr> iter(sessions()); |
|
|
|
// TODO - think about the UI when saving multiple history sessions, if there are more than two or |
|
// three then providing a URL for each one will be tedious |
|
|
|
// TODO - show a warning ( preferably passive ) if saving the history output fails |
|
// |
|
|
|
KFileDialog* dialog = new KFileDialog( QString(":konsole") /* check this */, |
|
QString(), QApplication::activeWindow() ); |
|
dialog->setOperationMode(KFileDialog::Saving); |
|
dialog->setConfirmOverwrite(true); |
|
|
|
QStringList mimeTypes; |
|
mimeTypes << "text/plain"; |
|
mimeTypes << "text/html"; |
|
dialog->setMimeFilter(mimeTypes,"text/plain"); |
|
|
|
// iterate over each session in the task and display a dialog to allow the user to choose where |
|
// to save that session's history. |
|
// then start a KIO job to transfer the data from the history to the chosen URL |
|
while ( iter.hasNext() ) |
|
{ |
|
SessionPtr session = iter.next(); |
|
|
|
dialog->setCaption( i18n("Save Output From %1",session->title(Session::NameRole)) ); |
|
|
|
int result = dialog->exec(); |
|
|
|
if ( result != QDialog::Accepted ) |
|
continue; |
|
|
|
KUrl url = dialog->selectedUrl(); |
|
|
|
if ( !url.isValid() ) |
|
{ // UI: Can we make this friendlier? |
|
KMessageBox::sorry( 0 , i18n("%1 is an invalid URL, the output could not be saved.",url.url()) ); |
|
continue; |
|
} |
|
|
|
KIO::TransferJob* job = KIO::put( url, |
|
-1, // no special permissions |
|
// overwrite existing files |
|
// do not resume an existing transfer |
|
// show progress information only for remote |
|
// URLs |
|
KIO::Overwrite | (url.isLocalFile() ? KIO::HideProgressInfo : KIO::DefaultFlags) |
|
// a better solution would be to show progress |
|
// information after a certain period of time |
|
// instead, since the overall speed of transfer |
|
// depends on factors other than just the protocol |
|
// used |
|
); |
|
|
|
|
|
SaveJob jobInfo; |
|
jobInfo.session = session; |
|
jobInfo.lastLineFetched = -1; // when each request for data comes in from the KIO subsystem |
|
// lastLineFetched is used to keep track of how much of the history |
|
// has already been sent, and where the next request should continue |
|
// from. |
|
// this is set to -1 to indicate the job has just been started |
|
|
|
if ( dialog->currentMimeFilter() == "text/html" ) |
|
jobInfo.decoder = new HTMLDecoder(); |
|
else |
|
jobInfo.decoder = new PlainTextDecoder(); |
|
|
|
_jobSession.insert(job,jobInfo); |
|
|
|
connect( job , SIGNAL(dataReq(KIO::Job*,QByteArray&)), |
|
this, SLOT(jobDataRequested(KIO::Job*,QByteArray&)) ); |
|
connect( job , SIGNAL(result(KJob*)), |
|
this, SLOT(jobResult(KJob*)) ); |
|
} |
|
|
|
dialog->deleteLater(); |
|
} |
|
void SaveHistoryTask::jobDataRequested(KIO::Job* job , QByteArray& data) |
|
{ |
|
// TODO - Report progress information for the job |
|
|
|
// PERFORMANCE: Do some tests and tweak this value to get faster saving |
|
const int LINES_PER_REQUEST = 500; |
|
|
|
SaveJob& info = _jobSession[job]; |
|
|
|
// transfer LINES_PER_REQUEST lines from the session's history |
|
// to the save location |
|
if ( info.session ) |
|
{ |
|
// note: when retrieving lines from the emulation, |
|
// the first line is at index 0. |
|
|
|
int sessionLines = info.session->emulation()->lineCount(); |
|
|
|
if ( sessionLines-1 == info.lastLineFetched ) |
|
return; // if there is no more data to transfer then stop the job |
|
|
|
int copyUpToLine = qMin( info.lastLineFetched + LINES_PER_REQUEST , |
|
sessionLines-1 ); |
|
|
|
QTextStream stream(&data,QIODevice::ReadWrite); |
|
info.decoder->begin(&stream); |
|
info.session->emulation()->writeToStream( info.decoder , info.lastLineFetched+1 , copyUpToLine ); |
|
info.decoder->end(); |
|
|
|
// if there are still more lines to process after this request |
|
// then insert a new line character |
|
// to ensure that the next block of lines begins on a new line |
|
// |
|
// FIXME - There is still an extra new-line at the end of the save data. |
|
if ( copyUpToLine <= sessionLines-1 ) |
|
{ |
|
stream << '\n'; |
|
} |
|
|
|
|
|
info.lastLineFetched = copyUpToLine; |
|
} |
|
} |
|
void SaveHistoryTask::jobResult(KJob* job) |
|
{ |
|
if ( job->error() ) |
|
{ |
|
KMessageBox::sorry( 0 , i18n("A problem occurred when saving the output.\n%1",job->errorString()) ); |
|
} |
|
|
|
TerminalCharacterDecoder * decoder = _jobSession[job].decoder; |
|
|
|
_jobSession.remove(job); |
|
|
|
delete decoder; |
|
|
|
// notify the world that the task is done |
|
emit completed(true); |
|
|
|
if ( autoDelete() ) |
|
deleteLater(); |
|
} |
|
void SearchHistoryTask::addScreenWindow( Session* session , ScreenWindow* searchWindow ) |
|
{ |
|
_windows.insert(session,searchWindow); |
|
} |
|
void SearchHistoryTask::execute() |
|
{ |
|
QMapIterator< SessionPtr , ScreenWindowPtr > iter(_windows); |
|
|
|
while ( iter.hasNext() ) |
|
{ |
|
iter.next(); |
|
executeOnScreenWindow( iter.key() , iter.value() ); |
|
} |
|
} |
|
|
|
void SearchHistoryTask::executeOnScreenWindow( SessionPtr session , ScreenWindowPtr window ) |
|
{ |
|
Q_ASSERT( session ); |
|
Q_ASSERT( window ); |
|
|
|
Emulation* emulation = session->emulation(); |
|
|
|
int selectionColumn = 0; |
|
int selectionLine = 0; |
|
|
|
window->getSelectionEnd(selectionColumn , selectionLine); |
|
|
|
if ( !_regExp.isEmpty() ) |
|
{ |
|
int pos = -1; |
|
const bool forwards = ( _direction == ForwardsSearch ); |
|
int startLine = selectionLine + window->currentLine() + ( forwards ? 1 : -1 ); |
|
// Temporary fix for #205495 |
|
if (startLine < 0) startLine = 0; |
|
const int lastLine = window->lineCount() - 1; |
|
QString string; |
|
|
|
//text stream to read history into string for pattern or regular expression searching |
|
QTextStream searchStream(&string); |
|
|
|
PlainTextDecoder decoder; |
|
decoder.setRecordLinePositions(true); |
|
|
|
//setup first and last lines depending on search direction |
|
int line = startLine; |
|
|
|
//read through and search history in blocks of 10K lines. |
|
//this balances the need to retrieve lots of data from the history each time |
|
//(for efficient searching) |
|
//without using silly amounts of memory if the history is very large. |
|
const int maxDelta = qMin(window->lineCount(),10000); |
|
int delta = forwards ? maxDelta : -maxDelta; |
|
|
|
int endLine = line; |
|
bool hasWrapped = false; // set to true when we reach the top/bottom |
|
// of the output and continue from the other |
|
// end |
|
|
|
//loop through history in blocks of <delta> lines. |
|
do |
|
{ |
|
// ensure that application does not appear to hang |
|
// if searching through a lengthy output |
|
QApplication::processEvents(); |
|
|
|
// calculate lines to search in this iteration |
|
if ( hasWrapped ) |
|
{ |
|
if ( endLine == lastLine ) |
|
line = 0; |
|
else if ( endLine == 0 ) |
|
line = lastLine; |
|
|
|
endLine += delta; |
|
|
|
if ( forwards ) |
|
endLine = qMin( startLine , endLine ); |
|
else |
|
endLine = qMax( startLine , endLine ); |
|
} |
|
else |
|
{ |
|
endLine += delta; |
|
|
|
if ( endLine > lastLine ) |
|
{ |
|
hasWrapped = true; |
|
endLine = lastLine; |
|
} else if ( endLine < 0 ) |
|
{ |
|
hasWrapped = true; |
|
endLine = 0; |
|
} |
|
} |
|
|
|
decoder.begin(&searchStream); |
|
emulation->writeToStream(&decoder, qMin(endLine,line) , qMax(endLine,line) ); |
|
decoder.end(); |
|
|
|
// line number search below assumes that the buffer ends with a new-line |
|
string.append('\n'); |
|
|
|
pos = -1; |
|
if (forwards) |
|
pos = string.indexOf(_regExp); |
|
else |
|
pos = string.lastIndexOf(_regExp); |
|
|
|
//if a match is found, position the cursor on that line and update the screen |
|
if ( pos != -1 ) |
|
{ |
|
int newLines = 0; |
|
QList<int> linePositions = decoder.linePositions(); |
|
while (newLines < linePositions.count() && linePositions[newLines] <= pos) |
|
newLines++; |
|
|
|
// ignore the new line at the start of the buffer |
|
newLines--; |
|
|
|
int findPos = qMin(line,endLine) + newLines; |
|
|
|
highlightResult(window,findPos); |
|
|
|
emit completed(true); |
|
|
|
return; |
|
} |
|
|
|
//clear the current block of text and move to the next one |
|
string.clear(); |
|
line = endLine; |
|
|
|
} while ( startLine != endLine ); |
|
|
|
// if no match was found, clear selection to indicate this |
|
window->clearSelection(); |
|
window->notifyOutputChanged(); |
|
} |
|
|
|
emit completed(false); |
|
} |
|
void SearchHistoryTask::highlightResult(ScreenWindowPtr window , int findPos) |
|
{ |
|
//work out how many lines into the current block of text the search result was found |
|
//- looks a little painful, but it only has to be done once per search. |
|
|
|
//kDebug(1211) << "Found result at line " << findPos; |
|
|
|
//update display to show area of history containing selection |
|
window->scrollTo(findPos); |
|
window->setSelectionStart( 0 , findPos - window->currentLine() , false ); |
|
window->setSelectionEnd( window->columnCount() , findPos - window->currentLine() ); |
|
window->setTrackOutput(false); |
|
window->notifyOutputChanged(); |
|
} |
|
|
|
SearchHistoryTask::SearchHistoryTask(QObject* parent) |
|
: SessionTask(parent) |
|
, _direction(ForwardsSearch) |
|
{ |
|
|
|
} |
|
void SearchHistoryTask::setSearchDirection( SearchDirection direction ) |
|
{ |
|
_direction = direction; |
|
} |
|
SearchHistoryTask::SearchDirection SearchHistoryTask::searchDirection() const |
|
{ |
|
return _direction; |
|
} |
|
void SearchHistoryTask::setRegExp(const QRegExp& expression) |
|
{ |
|
_regExp = expression; |
|
} |
|
QRegExp SearchHistoryTask::regExp() const |
|
{ |
|
return _regExp; |
|
} |
|
|
|
#include "SessionController.moc" |
|
|
|
|