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.

703 lines
21 KiB

//Author: Timothy Pearson <kb9vqf@pearsoncomputing.net>, (C) 2012
//Copyright: See COPYING file that comes with this distribution
// TDE MDI interface based on a (passable) tutorial by Andrea Bergia et al.
#include "remotemdi.h"
#include <cassert>
using namespace std;
#include <pwd.h>
#include <tdeapplication.h>
#include <tdelocale.h>
#include <kdebug.h>
#include <tdeconfig.h>
#include <tdemessagebox.h>
#include <tqlabel.h>
#include <tqtimer.h>
#include <tqlayout.h>
#include <kiconloader.h>
#include <kstdaction.h>
#include <kstatusbar.h>
#include <tdemdichildview.h>
#include <tdelistbox.h>
#include <tdeactionclasses.h>
#include <kedittoolbar.h>
#include <kkeydialog.h>
#include <libtdeldap.h>
#include "views/instrumentview.h"
#include "dialogs/selectserverdlg.h"
#define STATUSBAR_TIMEOUT_ID 5
RemoteMDI::RemoteMDI()
: KMdiMainFrm(0, "RemoteMDI", KMdi::ChildframeMode), m_children(0), m_rsvSvrSocket(NULL), connToServerConnecting(false), connToServerState(-1), connToServerTimeoutTimer(NULL)
{
setXMLFile("remotelabui.rc");
setIcon(SmallIcon("remote_laboratory_client"));
masterPollTimer = new TQTimer();
connect(masterPollTimer, SIGNAL(timeout()), this, SLOT(masterPoll()));
// Create some actions
KStdAction::close(this, SLOT(closeCurrent()), actionCollection());
KStdAction::quit(this, SLOT(close()), actionCollection());
TDEActionCollection *const ac = actionCollection();
setStandardToolBarMenuEnabled(true);
KStdAction::quit(TQT_TQOBJECT(this), TQT_SLOT(close()), ac);
KStdAction::configureToolbars(TQT_TQOBJECT(this), TQT_SLOT(configToolbars()), ac);
KStdAction::keyBindings(TQT_TQOBJECT(this), TQT_SLOT(configKeys()), ac);
connect_action = new TDEAction(i18n("Connect to Server"), "connect_creating", TDEShortcut(), TQT_TQOBJECT(this), TQT_SLOT(connectToServer()), ac, "connect_server");
disconnect_action = new TDEAction(i18n("Disconnect from Server"), "connect_no", TDEShortcut(), TQT_TQOBJECT(this), TQT_SLOT(disconnectFromServer()), ac, "disconnect_server");
setMenuForSDIModeSysButtons(menuBar());
createGUI(0);
// Add Window menu
if ( !isFakingSDIApplication() ) {
menuBar()->insertItem(i18n("&Window"), windowMenu(), -1, 4);
}
// Hide task bar as no windows are currently active
hideViewTaskBar();
// When we change view, change the status bar text
connect(this, SIGNAL(viewActivated(KMdiChildView*)), this, SLOT(currentChanged(KMdiChildView*)));
ac->setHighlightingEnabled(true);
connect(ac, TQT_SIGNAL(actionStatusText(const TQString&)), this, TQT_SLOT(updateStatusBarMainMessage(const TQString&) ));
connect(ac, TQT_SIGNAL(clearStatusText()), statusBar(), TQT_SLOT(clear()));
// Create the status bar
updateStatusBarMainMessage(i18n("No active instruments"));
KStatusBar* sb = statusBar();
if (sb) {
sb->insertItem(i18n("Unknown Time Remaining"), STATUSBAR_TIMEOUT_ID, 0, true);
}
processActions();
processLockouts();
showMaximized();
}
RemoteMDI::~RemoteMDI()
{
if (masterPollTimer) {
masterPollTimer->stop();
delete masterPollTimer;
}
while (m_pCurrentWindow) {
closeCurrent();
}
if (m_rsvSvrSocket) {
m_rsvSvrSocket->clearPendingData();
m_rsvSvrSocket->close();
delete m_rsvSvrSocket;
m_rsvSvrSocket = NULL;
}
}
void RemoteMDI::updateStatusBarMessage() {
TQString windowStatusBarMessage;
if (m_pCurrentWindow) {
windowStatusBarMessage = m_windowStatusBarMapping[TQT_TQOBJECT(m_pCurrentWindow)];
}
KStatusBar* sb = statusBar();
if (sb) {
sb->message(m_mainStatusBarMessage + ((windowStatusBarMessage != "")?" [" + i18n("Instrument") + ": " + windowStatusBarMessage + "]":""));
}
}
void RemoteMDI::updateStatusBarMainMessage(const TQString& message) {
m_mainStatusBarMessage = message;
updateStatusBarMessage();
}
void RemoteMDI::updateStatusBarWindowMessage(const TQString& message, const TQObject* window) {
const TQObject* windowObject = window;
if (!windowObject) {
windowObject = sender();
}
if (windowObject) {
m_windowStatusBarMapping[windowObject] = message;
}
updateStatusBarMessage();
}
void RemoteMDI::resizeEvent(TQResizeEvent *e) {
KMdiMainFrm::resizeEvent(e);
setSysButtonsAtMenuPosition();
}
void RemoteMDI::processActions() {
// Add dynamic actions
TDEActionCollection *const ac = actionCollection();
TDEAction* action;
ServiceType st;
for (ServiceList::Iterator it(m_activeStation.services.begin()); it != m_activeStation.services.end(); ++it) {
st = *it;
action = new TDEAction(i18n("Launch")+" "+st.name, st.clientLibrary, TDEShortcut(), TQT_TQOBJECT(this), TQT_SLOT(startModule()), ac, st.clientLibrary.ascii());
m_instrumentActionList.append(action);
}
plugActionList("instrumentMenu_actionlist", m_instrumentActionList);
plugActionList("instrumentToolBar_actionlist", m_instrumentActionList);
}
void RemoteMDI::startModule() {
const TDEAction* sendingAction = dynamic_cast<const TDEAction*>(sender());
if (sendingAction) {
bool serviceFound = false;
ServiceType st;
for (ServiceList::Iterator it(m_activeStation.services.begin()); it != m_activeStation.services.end(); ++it) {
st = *it;
if (st.clientLibrary == sendingAction->name()) {
serviceFound = true;
break;
}
}
if (!serviceFound) {
KMessageBox::error(this, i18n("<qt>The active laboratory workspace does not support the requested service</qt>"), i18n("Service Unavailable"));
return;
}
RemoteLab::InstrumentView* view = new RemoteLab::InstrumentView(st.clientLibrary, st.name, (mdiMode() == KMdi::ToplevelMode) ? 0 : this);
view->setName(st.clientLibrary.ascii());
connect(view, SIGNAL(statusMessageSet(const TQString&)), this, SLOT(updateStatusBarWindowMessage(const TQString&)));
if (st.singleInstance) {
const_cast<TDEAction*>(sendingAction)->setEnabled(false);
}
openNewWindow(view);
showViewTaskBar();
if (m_serverHost != "") {
view->connectServer(m_serverHost);
}
}
}
int RemoteMDI::getNewTicket() {
int ret = -1;
LDAPCredentials credentials;
KerberosTicketInfoList ticketList = LDAPManager::getKerberosTicketList();
if (ticketList.count() > 0) {
TQStringList princParts = TQStringList::split("@", ticketList[0].cachePrincipal);
credentials.username = princParts[0];
credentials.realm = princParts[1];
}
else {
struct passwd* pwd = getpwuid(geteuid());
if (pwd) {
credentials.username = TQString(pwd->pw_name);
}
}
int result = LDAPManager::getKerberosPassword(credentials, i18n("Please provide Kerberos credentials"), false, this);
if (result == KDialog::Accepted) {
TQString errorstring;
TQString service;
if (LDAPManager::obtainKerberosTicket(credentials, service, &errorstring) != 0) {
KMessageBox::error(this, i18n("<qt>Failed to obtain ticket<p>%1</qt>").arg(errorstring), i18n("Failed to obtain Kerberos ticket"));
}
else {
ret = 0;
}
}
return ret;
}
void RemoteMDI::finishConnectingToServer() {
if (!m_rsvSvrSocket) {
connToServerState = -1;
connToServerConnecting = false;
processLockouts();
return;
}
if (connToServerConnecting) {
switch(connToServerState) {
case 0:
if (!connToServerTimeoutTimer) {
connToServerTimeoutTimer = new TQTimer;
connToServerTimeoutTimer->start(5000, TRUE);
}
if ((m_rsvSvrSocket->state() == TQSocket::Connecting) || (m_rsvSvrSocket->state() == TQSocket::HostLookup)) {
if (!connToServerTimeoutTimer->isActive()) {
connToServerState = -3;
connToServerConnecting = false;
disconnectFromServer();
KMessageBox::error(this, i18n("<qt>Unable to establish connection to remote server</qt>"), i18n("Connection Failed"));
return;
}
}
else {
if (m_rsvSvrSocket->state() == TQSocket::Connected) {
printf("[DEBUG] Initial connection established...\n\r"); fflush(stdout);
m_rsvSvrSocket->setDataTimeout(5000);
m_rsvSvrSocket->setUsingKerberos(true);
delete connToServerTimeoutTimer;
connToServerTimeoutTimer= NULL;
connToServerState = 1;
}
else {
connToServerState = -1;
connToServerConnecting = false;
disconnectFromServer();
KMessageBox::error(this, i18n("<qt>Unable to establish connection to remote server</qt>"), i18n("Connection Failed"));
return;
}
}
break;
case 1:
if (m_rsvSvrSocket->kerberosStatus() == TDEKerberosClientSocket::KerberosInitializing) {
// Do nothing
}
else {
if (m_rsvSvrSocket->kerberosStatus() != TDEKerberosClientSocket::KerberosInUse) {
connToServerState = -1;
connToServerConnecting = false;
disconnectFromServer();
// Try to get a valid ticket
if (getNewTicket() == 0) {
// Retry connection if no obvious errors were detected
TQTimer::singleShot(0, this, SLOT(connectToServer()));
return;
}
else {
KMessageBox::error(this, i18n("<qt>Unable to establish Kerberos protocol with remote server<p>Please verify that you currently hold a valid Kerberos ticket</qt>"), i18n("Connection Failed"));
return;
}
}
else {
connect(m_rsvSvrSocket, SIGNAL(readyRead()), m_rsvSvrSocket, SLOT(processPendingData()));
m_rsvSvrSocket->processPendingData();
connToServerState = 2;
}
}
break;
case 2:
// Connection established!
// Read magic number and proto version from server
TQDataStream* ds = new TQDataStream(m_rsvSvrSocket);
ds->setPrintableData(true);
while (!m_rsvSvrSocket->canReadFrame()) {
tqApp->processEvents();
if (!m_rsvSvrSocket) {
return;
}
}
TQ_UINT32 magicnum;
TQ_UINT32 protover;
*ds >> magicnum;
*ds >> protover;
m_rsvSvrSocket->clearFrameTail();
printf("[DEBUG] Got magic number %d and protocol version %d\n\r", magicnum, protover); fflush(stdout);
if ((magicnum == MAGIC_NUMBER) && (protover == PROTOCOL_VERSION)) {
// Request server name
TQString serverName;
*ds << TQString("NAME");
m_rsvSvrSocket->writeEndOfFrame();
while (!m_rsvSvrSocket->canReadFrame()) {
tqApp->processEvents();
if (!m_rsvSvrSocket) {
return;
}
}
*ds >> serverName;
m_rsvSvrSocket->clearFrameTail();
// Set caption if a valid server name was received
if ((serverName != "") && (serverName != "ERRINVCMD")) {
setCaption(TQString("%1 - %2").arg(serverName).arg(kapp->caption()));
}
delete ds;
disconnect_action->setEnabled(true);
promptForStationType();
}
else {
delete ds;
disconnectFromServer();
KMessageBox::error(this, i18n("<qt>The remote server is not compatible with this client</qt>"), i18n("Connection Failed"));
}
connToServerState = 3;
connToServerConnecting = false;
masterPollTimer->start(0, TRUE);
processLockouts();
break;
}
TQTimer::singleShot(0, this, SLOT(finishConnectingToServer()));
}
}
void RemoteMDI::masterPoll() {
// Query current termination timestamp
if (m_rsvSvrSocket) {
if ((m_rsvSvrSocket->state() == TQSocket::Connected) && (!connToServerConnecting)) {
TQDataStream ds(m_rsvSvrSocket);
ds.setPrintableData(true);
TQ_ULLONG terminationStamp;
long long currentStamp;
ds << TQString("TSTP");
m_rsvSvrSocket->writeEndOfFrame();
while (!m_rsvSvrSocket->canReadFrame()) {
tqApp->processEvents();
if (!m_rsvSvrSocket) {
masterPollTimer->start(1000, TRUE);
return;
}
}
ds >> terminationStamp;
m_rsvSvrSocket->clearFrameTail();
currentStamp = TQDateTime::currentDateTime().toTime_t();
KStatusBar* sb = statusBar();
if (sb) {
if (terminationStamp == 0) {
sb->changeItem(i18n("Unlimited Time Remaining"), STATUSBAR_TIMEOUT_ID);
}
else {
long long difference = terminationStamp - currentStamp;
int seconds = 0;
int minutes = 0;
int hours = 0;
int days = 0;
if (difference >= 0) {
days = (difference / 86400);
difference = difference - (days * 86400);
hours = (difference / 3600);
difference = difference - (hours * 3600);
minutes = (difference / 60);
difference = difference - (minutes * 60);
seconds = difference;
}
TQString differenceString;
if (days > 0) {
differenceString.append(i18n("%1 day(s), ").arg(days));
}
if ((days > 0) || (hours > 0)) {
differenceString.append(i18n("%1 hours(s), ").arg(hours));
}
if ((days > 0) || (hours > 0) || (minutes > 0)) {
differenceString.append(i18n("%1 minutes(s), ").arg(minutes));
}
differenceString.append(i18n("%1 seconds(s)").arg(seconds));
sb->changeItem(i18n("%1 Remaining").arg(differenceString), STATUSBAR_TIMEOUT_ID);
}
}
}
}
masterPollTimer->start(1000, TRUE);
}
void RemoteMDI::connectToServer() {
if (m_rsvSvrSocket) {
if (m_rsvSvrSocket->state() != TQSocket::Idle) {
printf("[DEBUG] Not connecting because the socket is still in state %d\n\r", m_rsvSvrSocket->state()); fflush(stdout);
return;
}
}
connect_action->setEnabled(false);
disconnect_action->setEnabled(true);
// Connect to the central reservation/control server
if (!m_rsvSvrSocket) {
m_rsvSvrSocket = new TDEKerberosClientSocket(this);
connect(m_rsvSvrSocket, SIGNAL(connectionClosed()), this, SLOT(connectionClosedHandler()));
connect(m_rsvSvrSocket, TQT_SIGNAL(statusMessageUpdated(const TQString&)), this, TQT_SLOT(updateStatusBarMainMessage(const TQString&) ));
}
m_rsvSvrSocket->setServiceName("ulab");
TQStringList disallowedMechList;
disallowedMechList.append("GSS-SPNEGO");
m_rsvSvrSocket->setDisallowedMechanisms(disallowedMechList);
if (m_serverHost != "") {
m_rsvSvrSocket->setServerFQDN(m_serverHost);
m_rsvSvrSocket->connectToHost(m_serverHost, 4004);
// Finish connecting when appropriate
connToServerState = 0;
connToServerConnecting = true;
TQTimer::singleShot(0, this, SLOT(finishConnectingToServer()));
}
}
void RemoteMDI::promptForStationType() {
if (!m_rsvSvrSocket) {
return;
}
if (m_rsvSvrSocket->state() != TQSocket::Connected) {
return;
}
TQDataStream ds(m_rsvSvrSocket);
ds.setPrintableData(true);
// Request list of laboratory stations
StationList slist;
ds << TQString("LIST");
m_rsvSvrSocket->writeEndOfFrame();
while (!m_rsvSvrSocket->canReadFrame()) {
tqApp->processEvents();
if (!m_rsvSvrSocket) {
return;
}
}
ds >> slist;
m_rsvSvrSocket->clearFrameTail();
SelectServerDialog select(this, 0, slist);
const int ret = select.exec();
if (ret == KDialog::Accepted) {
TQString result;
ds << TQString("BIND");
m_rsvSvrSocket->writeEndOfFrame();
ds << select.m_selectedStation;
m_rsvSvrSocket->writeEndOfFrame();
while (!m_rsvSvrSocket->canReadFrame()) {
tqApp->processEvents();
if (!m_rsvSvrSocket) {
return;
}
}
ds >> result;
m_rsvSvrSocket->clearFrameTail();
if (result == "OK") {
// Success!
m_activeStation = select.m_selectedStation;
processActions();
}
else if (result == "ERRUNAVAL") {
KMessageBox::error(this, i18n("<qt>No stations of the specified type are currently available<p>Please try again later</qt>"), i18n("Insufficient Laboratory Resources"));
disconnectFromServer();
}
else if (result == "ERRPREVCN") {
KMessageBox::error(this, i18n("<qt>You are already connected to a laboratory station<p>Please disconnect and try again</qt>"), i18n("Multiple Connections Detected"));
disconnectFromServer();
}
else {
KMessageBox::error(this, i18n("<qt>Unknown server error<p>Please reconnect and try again</qt>"), i18n("Internal Error"));
disconnectFromServer();
}
}
else {
disconnectFromServer();
}
}
void RemoteMDI::disconnectFromServer() {
connect_action->setEnabled(false);
disconnect_action->setEnabled(false);
m_instrumentActionList.clear();
unplugActionList("instrumentMenu_actionlist");
unplugActionList("instrumentToolBar_actionlist");
// Close all windows
closeAllViews();
if (m_rsvSvrSocket) {
m_rsvSvrSocket->clearPendingData();
m_rsvSvrSocket->close();
delete m_rsvSvrSocket;
m_rsvSvrSocket = NULL;
}
connect_action->setEnabled(true);
processLockouts();
}
void RemoteMDI::connectionClosedHandler() {
disconnectFromServer();
KMessageBox::error(this, i18n("<qt>The remote server has closed the connection</qt>"), i18n("Connection Terminated"));
}
void RemoteMDI::processLockouts() {
bool connected = false;
if (m_rsvSvrSocket) {
connected = ((m_rsvSvrSocket->state() == TQSocket::Connected) && (connToServerConnecting == false) && (connToServerState > 0));
}
connect_action->setEnabled(!connected);
disconnect_action->setEnabled(connected);
for (TQPtrList<TDEAction>::Iterator it(m_instrumentActionList.begin()); it != m_instrumentActionList.end(); ++it) {
(*it)->setEnabled(connected);
}
if (!connected) {
KStatusBar* sb = statusBar();
if (sb) {
sb->changeItem(i18n("Unknown Time Remaining"), STATUSBAR_TIMEOUT_ID);
}
}
}
void RemoteMDI::configToolbars() {
KEditToolbar dialog(factory(), this);
dialog.showButtonApply(false);
if (dialog.exec()) {
applyMainWindowSettings(kapp->config(), "window");
}
}
void RemoteMDI::configKeys() {
KKeyDialog::configure(actionCollection(), this);
}
void RemoteMDI::setServerHost(TQString server) {
m_serverHost = server;
connectToServer();
}
void RemoteMDI::openNewWindow(KMdiChildView *view) {
// Add a child view
m_children++;
// The child view will be our child only if we aren't in Toplevel mode
if (!view) {
view = new KMdiChildView(i18n("View %1").arg(m_children), (mdiMode() == KMdi::ToplevelMode) ? 0 : this);
}
(new TQHBoxLayout(view))->setAutoAdd( true );
// Add to the MDI and set as current
if (mdiMode() == KMdi::ToplevelMode) {
addWindow(view, KMdi::Detach);
}
else {
addWindow(view);
}
currentChanged(view);
// Handle termination
connect(view, SIGNAL(childWindowCloseRequest(KMdiChildView*)), this, SLOT(childClosed(KMdiChildView*)));
}
void RemoteMDI::childWindowCloseRequest(KMdiChildView *pWnd) {
RemoteLab::InstrumentView* iview = dynamic_cast<RemoteLab::InstrumentView*>(pWnd);
if (iview) {
// Give the child a chance to finish what it was doing and exit cleanly (i.e. without crashing!)
iview->closeConnections();
iview->hide();
KMdiMainFrm::childWindowCloseRequest(pWnd);
}
}
void RemoteMDI::currentChanged(KMdiChildView *current) {
RemoteLab::InstrumentView* view = dynamic_cast<RemoteLab::InstrumentView*>(current);
// Plug/unplug menus
if (view) {
unplugActionList("selectedInstrument_actionlist");
plugActionList("selectedInstrument_actionlist", view->menuActionList());
}
// Update status bar and list box
updateStatusBarMainMessage(i18n("Instrument %1 activated").arg(current->tabCaption()));
}
void RemoteMDI::closeCurrent() {
// If there's a current view, close it
if (m_pCurrentWindow) {
closeSpecifiedWindow(m_pCurrentWindow);
}
}
void RemoteMDI::closeSpecifiedWindow(KMdiChildView *window) {
if (window) {
// Notify the status bar of the removal of the window
updateStatusBarWindowMessage(TQString::null, TQT_TQOBJECT(window));
updateStatusBarMainMessage(i18n("Instrument %1 removed").arg(window->tabCaption()));
// Unplug menus
unplugActionList("selectedInstrument_actionlist");
// We could also call removeWindowFromMdi, but it doesn't delete the
// pointer. This way, we're sure that the view will get deleted.
closeWindow(window);
// Synchronize combo box
if (m_pCurrentWindow) {
currentChanged(m_pCurrentWindow);
}
}
}
void RemoteMDI::childClosed(KMdiChildView * w) {
assert(w);
// Set as active
w->activate();
assert(w == m_pCurrentWindow);
// Unplug menus
unplugActionList("selectedInstrument_actionlist");
// Notify the status bar of the removal of the window
updateStatusBarWindowMessage(TQString::null, TQT_TQOBJECT(w));
updateStatusBarMainMessage(i18n("Instrument %1 removed").arg(w->tabCaption()));
// Re-enable associated action
RemoteLab::InstrumentView* view = dynamic_cast<RemoteLab::InstrumentView*>(w);
if (view) {
TQString libraryName = view->name();
for (TQPtrList<TDEAction>::Iterator it(m_instrumentActionList.begin()); it != m_instrumentActionList.end(); ++it) {
if ((*it)->name() == libraryName) {
(*it)->setEnabled(true);
}
}
}
// Remove status bar text
m_windowStatusBarMapping.remove(TQT_TQOBJECT(w));
// Remove the view from MDI, BUT DO NOT DELETE IT! It is automatically deleted by TQt since it was closed.
removeWindowFromMdi(w);
}
bool RemoteMDI::queryClose() {
// Close all open connections
KMdiIterator<KMdiChildView*> *it = createIterator();
while (it->currentItem()) {
KMdiChildView *c = dynamic_cast<KMdiChildView*>(it->currentItem());
if (c) {
RemoteLab::InstrumentView* iview = dynamic_cast<RemoteLab::InstrumentView*>(c);
if (iview) {
iview->closeConnections();
}
}
it->next();
}
deleteIterator(it);
// Save current MDI settings (window positions, etc.)
// FIXME
TDEConfig *c = kapp->config();
c->sync();
// Allow this window to close
return true;
}
#include "remotemdi.moc"