//Author: Timothy Pearson , (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 using namespace std; #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(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("The active laboratory workspace does not support the requested service"), 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(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("Failed to obtain ticket

%1").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("Unable to establish connection to remote server"), 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("Unable to establish connection to remote server"), 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("Unable to establish Kerberos protocol with remote server

Please verify that you currently hold a valid Kerberos ticket"), 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("The remote server is not compatible with this client"), 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("No stations of the specified type are currently available

Please try again later"), i18n("Insufficient Laboratory Resources")); disconnectFromServer(); } else if (result == "ERRPREVCN") { KMessageBox::error(this, i18n("You are already connected to a laboratory station

Please disconnect and try again"), i18n("Multiple Connections Detected")); disconnectFromServer(); } else { KMessageBox::error(this, i18n("Unknown server error

Please reconnect and try again"), 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("The remote server has closed the connection"), 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::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(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(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(w); if (view) { TQString libraryName = view->name(); for (TQPtrList::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 *it = createIterator(); while (it->currentItem()) { KMdiChildView *c = dynamic_cast(it->currentItem()); if (c) { RemoteLab::InstrumentView* iview = dynamic_cast(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"