
/***************************************************************************
 *                                                                         *
 *   KNetLoad is copyright (c) 1999-2000, Markus Gustavsson                *
 *                         (c) 2002, Ben Burton                            *
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

#include "devicedialog.h"
#include "knetdock.h"
#include "knetload.h"
#include "knetproc.h"
#include "scaledialog.h"

#include <kaction.h>
#include <kconfig.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kpopupmenu.h>
#include <qdir.h>

#define DEFAULT_SCALE 128000
#define MAX_NET_DEV_LINE 512

KNetLoad::KNetLoad(QWidget *parent, const char *name) :
        StatPopup(false, parent, name) {
    // Create the raw network data reader.
    proc = new KNetProc();

// Stock device names are not i18n()ed since they're literal
// interface names.
#ifndef Q_OS_LINUX
    stockDevice.push_back("lo");    stockDeviceIconOff.push_back("devlo");    stockDeviceIconOn.push_back("devloon");
    stockDevice.push_back("eth0");  stockDeviceIconOff.push_back("deveth0");  stockDeviceIconOn.push_back("deveth0on");
    stockDevice.push_back("ppp0");  stockDeviceIconOff.push_back("devppp0");  stockDeviceIconOn.push_back("devppp0on");
    stockDevice.push_back("ippp0"); stockDeviceIconOff.push_back("devippp0"); stockDeviceIconOn.push_back("devippp0on");
#else
    if ( QDir::root().exists("/sys/class/net") )
    { // Exists /sys, 2.6 series kernel
        QDir sys("/sys/class/net");
        QStringList l = sys.entryList();
        for(QStringList::iterator it = l.begin(); it != l.end(); it++)
        {
            if ( (*it)[0] == '.' )
                continue;
            
            stockDevice.push_back( *it );
            if ( *it == "lo" ) {
                stockDeviceIconOn.push_back("devloon");
                stockDeviceIconOff.push_back("devlo");
            } else if ( (*it).find("eth") == 0 ) {
                stockDeviceIconOn.push_back("deveth0on");
                stockDeviceIconOff.push_back("deveth0");
            } else if ( (*it).find("ppp") == 0 ) {
                stockDeviceIconOn.push_back("devppp0on");
                stockDeviceIconOff.push_back("devppp0");
            } else if ( (*it).find("ippp") == 0 ) {
                stockDeviceIconOn.push_back("devippp0on");
                stockDeviceIconOff.push_back("devippp0");
            } else {
                stockDeviceIconOn.push_back("devotheron");
                stockDeviceIconOff.push_back("devother");
            }
        }
    } else { // Doesn't exists, kernel 2.4 or earlier
        static FILE* fd;
        static char line[MAX_NET_DEV_LINE];
        static char* pos;
        static char* iface;

    
        if ((fd = fopen("/proc/net/dev", "r")) == 0)
            return;

        // Read the unwanted header lines.
        fgets(line, MAX_NET_DEV_LINE, fd);
        fgets(line, MAX_NET_DEV_LINE, fd);

        // Read through the remaining lines until we find all devices
        while (! feof(fd)) {
            fgets(line, MAX_NET_DEV_LINE, fd);

            // Find the interface name for this line.
            for (iface = line; *iface == ' '; iface++)
                ; // (skip initial spaces)
            for (pos = iface; *pos != ':' && *pos != 0; pos++)
                ; // (move to next ':' or end of string)
            if (*pos == 0)
                continue; // (was not ':')
            *pos = 0;

            // Now iface points to a null-terminated string containing the
            // interface name for this particular line.
            stockDevice.push_back( iface );
            if ( strncmp(iface, "lo", 2) == 0 ) {
                stockDeviceIconOn.push_back("devloon");
                stockDeviceIconOff.push_back("devlo");
            } else if ( strncmp(iface, "eth", 3) == 0 ) {
                stockDeviceIconOn.push_back("deveth0on");
                stockDeviceIconOff.push_back("deveth0");
            } else if ( strncmp(iface, "ppp", 3) == 0 ) {
                stockDeviceIconOn.push_back("devppp0on");
                stockDeviceIconOff.push_back("devppp0");
            } else if ( strncmp(iface, "ippp", 4) == 0 ) {
                stockDeviceIconOn.push_back("devippp0on");
                stockDeviceIconOff.push_back("devippp0");
            } else {
                stockDeviceIconOn.push_back("devotheron");
                stockDeviceIconOff.push_back("devother");
            }
        }
        fclose(fd);
    }
#endif
    
    // Set up actions and read the config file.
    setupActions();

    // Create system tray windows.
    dock[0] = new KNetDock(0, true, this); // In
    dock[1] = new KNetDock(1, false, this); // Out

    // Initialise the pop-up window.
    readPopupState();

    // Off we go!
    requestResize();
    if (isActive())
        startUpdates();
}

KNetLoad::~KNetLoad() {
    delete proc;
}

void KNetLoad::setDevice(const QString& newDevice) {
    proc->setDevice(newDevice);
    clearHistory();
    updateDeviceMenus();

    requestResize();

    config->setGroup("General Options");
    config->writeEntry("Device", newDevice);
    config->sync();
}

void KNetLoad::setDevice(int deviceIndex) {
    // Is it one of the stock devices?
    if ((deviceIndex >= 0) && !stockDevice[deviceIndex].isEmpty()) {
        setDevice(stockDevice[deviceIndex]);
        return;
    }

    // It's a custom device.  Open a dialog.
    DeviceDialog dlg(proc->getDevice(), firstDock());
    if (dlg.exec()) {
        QString newDevice = dlg.getDevice().stripWhiteSpace();
        if (newDevice.isEmpty())
            KMessageBox::error(firstDock(),
                i18n("The device name cannot be empty."));
        else {
            setDevice(newDevice);
            return;
        }
    }

    // If we fell through, update the menus anyway in case we
    // inadvertently changed a checked state.
    updateDeviceMenus();
}

void KNetLoad::setScaleIn(int scale) {
    if (scale <= 0) {
        // Select a non-standard scale.
        ScaleDialog dlg(scaleIn / 1000,
            i18n("Select Scale (In)"), firstDock());
        if (dlg.exec()) {
            scale = dlg.getScale();
            if (scale <= 0)
                return;
            // Convert to bits per second and fall through.
            scale *= 1000;
        } else
            return;
    }

    scaleIn = scale;
    updateScaleInMenus();

    config->setGroup("General Options");
    config->writeEntry("ScaleIn", scale);
    config->sync();
}

void KNetLoad::setScaleOut(int scale) {
    // Note that, for scale (out), scale == 0 means to use the same as
    // scale (in).
    if (scale < 0) {
        // Select a non-standard scale.
        ScaleDialog dlg(scaleOut ? scaleOut / 1000 : scaleIn / 1000,
            i18n("Select Scale (Out)"), firstDock());
        if (dlg.exec()) {
            scale = dlg.getScale();
            if (scale <= 0)
                return;
            // Convert to bits per second and fall through.
            scale *= 1000;
        } else
            return;
    }

    scaleOut = scale;
    updateScaleOutMenus();

    config->setGroup("General Options");
    config->writeEntry("ScaleOut", scale);
    config->sync();
}

QString KNetLoad::dockName(int which) const {
    return (which == 0 ? i18n("In") : i18n("Out"));
}

QColor KNetLoad::defaultDockColor(int which) const {
    return (which == 0 ? QColor(0, 0, 255) : QColor(255, 0, 255));
}

void KNetLoad::setupCustomActions() {
    // There are no device actions; instead menu items are used
    // directly.
    proc->setDevice(config->readEntry("Device", "eth0"));

    // There are no scaling actions; instead menu items are used
    // directly.
    scaleIn = config->readNumEntry("ScaleIn", DEFAULT_SCALE);
    scaleOut = config->readNumEntry("ScaleOut", 0 /* same as in */);
}

void KNetLoad::insertCustomItems(KPopupMenu* menu) {
    // Device menu.  The menu item IDs are indices into the
    // stockDevice[] array.
    KPopupMenu* deviceMenu = new KPopupMenu(menu);
    deviceMenus.append(deviceMenu);
    deviceMenu->setCheckable(true);
    for (int i = 0; i < stockDevice.count(); i++)
        deviceMenu->insertItem(SmallIcon(stockDeviceIconOff[i]),
            stockDevice[i], i);
    deviceMenu->insertItem(SmallIcon("devother"), i18n("Other..."));
    updateDeviceMenu(deviceMenu);
    connect(deviceMenu, SIGNAL(activated(int)), this, SLOT(setDevice(int)));
    menu->insertItem(SmallIcon("devselect"), i18n("&Device"), deviceMenu);

    // Scale menus.  The menu item IDs are the scales themselves (in bits
    // per second), to make event handling sane.
    KPopupMenu* scaleInMenu = new KPopupMenu(menu);
    scaleInMenus.append(scaleInMenu);
    scaleInMenu->setCheckable(true);
    scaleInMenu->insertItem(i18n("28.8KBit/s"), 28800);
    scaleInMenu->insertItem(i18n("33.6KBit/s"), 33600);
    scaleInMenu->insertItem(i18n("56KBit/s"), 56000);
    scaleInMenu->insertItem(i18n("64KBit/s"), 64000);
    scaleInMenu->insertItem(i18n("128KBit/s"), 128000);
    scaleInMenu->insertItem(i18n("256KBit/s"), 256000);
    scaleInMenu->insertItem(i18n("512KBit/s"), 512000);
    scaleInMenu->insertItem(i18n("1MBit/s"), 1000000);
    scaleInMenu->insertItem(i18n("2MBit/s"), 2000000);
    scaleInMenu->insertItem(i18n("10MBit/s"), 10000000);
    scaleInMenu->insertItem(i18n("Other..."));
    updateScaleInMenu(scaleInMenu);
    connect(scaleInMenu, SIGNAL(activated(int)), this, SLOT(setScaleIn(int)));
    menu->insertItem(SmallIcon("scalein"), i18n("&Scale (In)"), scaleInMenu);

    KPopupMenu* scaleOutMenu = new KPopupMenu(menu);
    scaleOutMenus.append(scaleOutMenu);
    scaleOutMenu->setCheckable(true);
    scaleOutMenu->insertItem(i18n("28.8KBit/s"), 28800);
    scaleOutMenu->insertItem(i18n("33.6KBit/s"), 33600);
    scaleOutMenu->insertItem(i18n("56KBit/s"), 56000);
    scaleOutMenu->insertItem(i18n("64KBit/s"), 64000);
    scaleOutMenu->insertItem(i18n("128KBit/s"), 128000);
    scaleOutMenu->insertItem(i18n("256KBit/s"), 256000);
    scaleOutMenu->insertItem(i18n("512KBit/s"), 512000);
    scaleOutMenu->insertItem(i18n("1MBit/s"), 1000000);
    scaleOutMenu->insertItem(i18n("2MBit/s"), 2000000);
    scaleOutMenu->insertItem(i18n("10MBit/s"), 10000000);
    scaleOutMenu->insertItem(i18n("Other..."));
    scaleOutMenu->insertSeparator();
    scaleOutMenu->insertItem(i18n("Same as for in"), 0);
    updateScaleOutMenu(scaleOutMenu);
    connect(scaleOutMenu, SIGNAL(activated(int)), this, SLOT(setScaleOut(int)));
    menu->insertItem(SmallIcon("scaleout"), i18n("&Scale (Out)"), scaleOutMenu);

    // The final separator.
    menu->insertSeparator();
}

void KNetLoad::takeReadingInternal() {
    proc->readLoad();

    // Rates in bits per second.
    bitRateIn = proc->recentBytesIn() * 8.0 /
        (((float) getSpeed()) / 1000.0);
    bitRateOut = proc->recentBytesOut() * 8.0 /
        (((float) getSpeed()) / 1000.0);

    // Convert to percentages.
    upper[0] = (int) (100.0 * bitRateIn / ((float) scaleIn));
    upper[1] = (int) (100.0 * bitRateOut /
        ((float) (scaleOut ? scaleOut : scaleIn)));

    if (upper[0] < 0)
        upper[0] = 0;
    if (upper[1] < 0)
        upper[1] = 0;
    if (upper[0] > 100)
        upper[0] = 100;
    if (upper[1] > 100)
        upper[1] = 100;

    if (isVisible())
        fullReading = i18n(
            "Current In: %1 KBit/s, Total In: %2 MB.\n"
            "Current Out: %3 KBit/s, Total Out: %4 MB.")
            .arg((int)((bitRateIn  + 50) / 100) / 10.0).arg(proc->totalMbIn())
            .arg((int)((bitRateOut + 50) / 100) / 10.0).arg(proc->totalMbOut());
}

void KNetLoad::updateDeviceMenu(KPopupMenu* menu) {
    const QString& dev = proc->getDevice();

    // Update the checked/unchecked states of menu items.
    bool found = false;
    int id;
    int otherId = -1;
    for (unsigned index = 0; index < menu->count(); index++) {
        id = menu->idAt(index);

        if (id >= 0 && dev == menu->text(id)) {
            // This is our device.
            found = true;
            if (! menu->isItemChecked(id)) {
                menu->setItemChecked(id, true);
                menu->changeItem(id, SmallIcon(stockDeviceIconOn[id]),
                    stockDevice[id]);
            }
        } else {
            // This is not our device.
            if (menu->isItemChecked(id)) {
                menu->setItemChecked(id, false);
                menu->changeItem(id, SmallIcon(stockDeviceIconOff[id]),
                    stockDevice[id]);
            }
            if (id < 0)
                if (! menu->text(id).isEmpty())
                    otherId = id;
        }
    }

    // Update the "other" item text and checked state.
    if (found) {
        menu->changeItem(otherId, SmallIcon("devother"), i18n("Other..."));
        menu->setItemChecked(otherId, false);
    } else {
        menu->changeItem(otherId, SmallIcon("devotheron"),
            QString(i18n("Other (%1)...")).arg(dev));
        menu->setItemChecked(otherId, true);
    }
}

void KNetLoad::updateDeviceMenus() {
    for (KPopupMenu* menu = deviceMenus.first(); menu;
            menu = deviceMenus.next())
        updateDeviceMenu(menu);
}

void KNetLoad::updateScaleInMenu(KPopupMenu* menu) {
    // Update the checked/unchecked states of menu items.
    bool found = false;
    int id;
    int otherId = -1;
    for (unsigned index = 0; index < menu->count(); index++) {
        id = menu->idAt(index);

        if (id == scaleIn) {
            menu->setItemChecked(id, true);
            found = true;
        } else {
            menu->setItemChecked(id, false);
            if (id < 0)
                if (! menu->text(id).isEmpty())
                    otherId = id;
        }
    }

    // Update the "other" item text.
    if (found)
        menu->changeItem(otherId, i18n("Other..."));
    else {
        QString text = i18n("Other (%1KBit/s)...").arg(int(((float) scaleIn) / 1000));
        menu->changeItem(otherId, text);
    }

    // Check the "other" item if necessary.
    if (! found)
        menu->setItemChecked(otherId, true);
}

void KNetLoad::updateScaleInMenus() {
    for (KPopupMenu* menu = scaleInMenus.first(); menu;
            menu = scaleInMenus.next())
        updateScaleInMenu(menu);
}

void KNetLoad::updateScaleOutMenu(KPopupMenu* menu) {
    // Update the checked/unchecked states of menu items.
    bool found = false;
    int id;
    int otherId = -1;
    for (unsigned index = 0; index < menu->count(); index++) {
        id = menu->idAt(index);

        if (id == scaleOut) {
            menu->setItemChecked(id, true);
            found = true;
        } else {
            menu->setItemChecked(id, false);
            if (id < 0)
                if (! menu->text(id).isEmpty())
                    otherId = id;
        }
    }

    // Update the "other" item text.
    if (found)
        menu->changeItem(otherId, i18n("Other..."));
    else {
        QString text = i18n("Other (%1KBit/s)...").arg(int(((float) scaleOut) / 1000));
        menu->changeItem(otherId, text);
    }

    // Check the "other" item if necessary.
    if (! found)
        menu->setItemChecked(otherId, true);
}

void KNetLoad::updateScaleOutMenus() {
    for (KPopupMenu* menu = scaleOutMenus.first(); menu;
            menu = scaleOutMenus.next())
        updateScaleOutMenu(menu);
}


#include "knetload.moc"
