// Remember.cc for Fluxbox Window Manager
// Copyright (c) 2002 Xavier Brouckaert
// Copyright (c) 2003 Henrik Kinnunen (fluxgen at users.sourceforge.net)
//                and Simon Bowden    (rathnor at users.sourceforge.net)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

// $Id: Remember.cc,v 1.33 2003/12/19 00:48:41 fluxgen Exp $

#include "Remember.hh"
#include "ClientPattern.hh"
#include "Screen.hh"
#include "Window.hh"
#include "WinClient.hh"
#include "FbMenu.hh"
#include "FbCommands.hh"
#include "fluxbox.hh"

#include "FbTk/StringUtil.hh"
#include "FbTk/MenuItem.hh"
#include "FbTk/App.hh"


#include <X11/Xlib.h>

//use GNU extensions
#ifndef	 _GNU_SOURCE
#define	 _GNU_SOURCE
#endif // _GNU_SOURCE


#include <iostream>
#include <fstream>
#include <string>
#include <memory>
#include <set>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif // HAVE_CONFIG_H

#ifdef HAVE_SSTREAM
#include <sstream>
#define FB_istringstream istringstream
#elif HAVE_STRSTREAM 
#include <strstream>
#define FB_istringstream istrstream
#else
#error "You dont have sstream or strstream headers!"
#endif // HAVE_STRSTREAM

using namespace std;

#ifndef    MAXPATHLEN
#define    MAXPATHLEN 255
#endif // MAXPATHLEN

namespace {

class RememberMenuItem : public FbTk::MenuItem {
public:
    RememberMenuItem(const char *label, Remember &remember,
                     FluxboxWindow &fbwin,
                     Remember::Attribute attrib) :
        FbTk::MenuItem(label), m_remember(remember), 
        m_win(fbwin), m_attrib(attrib) {}

    bool isSelected() const {
        return m_remember.isRemembered(m_win.winClient(), m_attrib);
    }

    bool isEnabled() const {
        if (m_attrib != Remember::REM_JUMPWORKSPACE) 
            return true;
        else 
            return (m_remember.isRemembered(m_win.winClient(), Remember::REM_WORKSPACE));
    }

    void click(int button, int time) {
        if (isSelected()) {
            m_remember.forgetAttrib(m_win.winClient(), m_attrib);
        } else {
            m_remember.rememberAttrib(m_win.winClient(), m_attrib);
        }
        m_remember.save();
        FbTk::MenuItem::click(button, time);
    }

private:
    // my remember manager
    Remember &m_remember;
    FluxboxWindow &m_win;
    Remember::Attribute m_attrib;
};

FbTk::Menu *createRememberMenu(Remember &remember, FluxboxWindow &win, bool enabled) {
    // each fluxboxwindow has its own windowmenu
    // so we also create a remember menu just for it...
    FbTk::Menu *menu = win.screen().createMenu("");

    // if enabled, then we want this to be a unavailable menu
    if (!enabled) {
        FbTk::MenuItem *item = new FbTk::MenuItem("unavailable");
        item->setEnabled(false);
        menu->insert(item);
        menu->update();
        return menu;
    }
    
    // TODO: nls
    menu->insert(new RememberMenuItem("Workspace", remember, win,
                                      Remember::REM_WORKSPACE));
    menu->insert(new RememberMenuItem("Jump to workspace", remember, win,
                                      Remember::REM_JUMPWORKSPACE));
    menu->insert(new RememberMenuItem("Dimensions", remember, win,
                                      Remember::REM_DIMENSIONS));
    menu->insert(new RememberMenuItem("Position", remember, win,
                                      Remember::REM_POSITION));
    menu->insert(new RememberMenuItem("Sticky", remember, win,
                                      Remember::REM_STUCKSTATE));
    menu->insert(new RememberMenuItem("Decorations", remember, win,
                                      Remember::REM_DECOSTATE));
    menu->insert(new RememberMenuItem("Shaded", remember, win,
                                      Remember::REM_SHADEDSTATE));
    menu->insert(new RememberMenuItem("Layer", remember, win,
                                      Remember::REM_LAYER));
    menu->insert(new RememberMenuItem("Save on close", remember, win,
                                      Remember::REM_SAVEONCLOSE));

    menu->update();
    return menu;
};

// offset is the offset in the string that we start looking from
// return true if all ok, false on error
bool handleStartupItem(const string &line, int offset) {
    int next = 0;
    string str;
    int screen = 0;

    // accept some options, for now only "screen=NN"
    // these option are given in parentheses before the command
    next = FbTk::StringUtil::getStringBetween(str, 
                                              line.c_str() + offset, 
                                              '(', ')');
    if (next > 0) {
        // there are some options
        string option;
        int pos = str.find('=');
        bool error = false;
        if (pos > 0) {
            option = str.substr(0, pos);
            if (option == "screen") {
                FB_istringstream iss(str.c_str() + pos + 1);
                iss >> screen;
            } else {
                error = true;
            }
        } else {
            error = true;
        }
        if (error) {
            cerr<<"Error parsing startup options."<<endl;
            return false;
        }
    } else {
        next = 0;
    }

    next = FbTk::StringUtil::getStringBetween(str, 
                                              line.c_str() + offset + next, 
                                              '{', '}');

    if (next <= 0) {
        cerr<<"Error parsing [startup] at column "<<offset<<" - expecting {command}."<<endl;
        return false;
    } else {
        FbCommands::ExecuteCmd *tmp_exec_cmd = new FbCommands::ExecuteCmd(str, screen);
#ifdef DEBUG
        cerr<<"Executing startup command '"<<str<<"' on screen "<<screen<<endl;
#endif // DEBUG
        tmp_exec_cmd->execute();
        delete tmp_exec_cmd;
        return true;
    }
};

}; // end anonymous namespace


Application::Application(bool grouped)
    : is_grouped(grouped),
      group(0)
{
    workspace_remember =
	dimensions_remember =
	position_remember =
	stuckstate_remember =
	decostate_remember =
	shadedstate_remember =
	tabstate_remember =
	jumpworkspace_remember =
        layer_remember =
	save_on_close_remember = false;
}

/********************************************************
 * Remember *
 ************/

Remember::Remember() {
    enableUpdate();
    load();
}

Remember::~Remember() {
    // free our resources

    // the patterns free the "Application"s
    // the client mapping shouldn't need cleaning
    Patterns::iterator it;
    std::set<Application *> all_apps; // no duplicates
    while (!m_pats.empty()) {
        it = m_pats.begin();
        delete it->first; // ClientPattern
        all_apps.insert(it->second); // Application, not necessarily unique
        m_pats.erase(it);
    }

    std::set<Application *>::iterator ait = all_apps.begin(); // no duplicates
    while (ait != all_apps.end()) {
        delete (*ait);
        ++ait;
    }
}

Application* Remember::find(WinClient &winclient) {
    // if it is already associated with a application, return that one
    // otherwise, check it against every pattern that we've got
    Clients::iterator wc_it = m_clients.find(&winclient);
    if (wc_it != m_clients.end())
        return wc_it->second;
    else {
        Patterns::iterator it = m_pats.begin();
        for (; it != m_pats.end(); it++) 
            if (it->first->match(winclient)) {
                it->first->addMatch();
                m_clients[&winclient] = it->second;
                return it->second;
            }
    }
    // oh well, no matches
    return 0;
}

Application * Remember::add(WinClient &winclient) {
    ClientPattern *p = new ClientPattern();
    Application *app = new Application(false);
    // by default, we match against the WMClass of a window.
    p->addTerm(p->getProperty(ClientPattern::NAME, winclient), ClientPattern::NAME);
    m_clients[&winclient] = app;
    p->addMatch();
    m_pats.push_back(make_pair(p, app));
    return app;
}

int Remember::parseApp(ifstream &file, Application &app, string *first_line) {
    string line;
    int row = 0;
    while (! file.eof()) {
        if (first_line || getline(file, line)) {
            if (first_line) {
                line = *first_line;
                first_line = 0;
            }

            row++;
            if (line[0] == '#')
                continue;  //the line is commented
            int parse_pos = 0, err = 0;
            string str_key, str_option, str_label;
            err = FbTk::StringUtil::getStringBetween(str_key, 
                                                     line.c_str(), 
                                                     '[', ']');
            if (err > 0) {
                int tmp;
                tmp= FbTk::StringUtil::getStringBetween(str_option,
                                                        line.c_str() + err,
                                                        '(', ')');
                if (tmp>0)
                  err += tmp;
            }
            if (err > 0 ) {
                parse_pos += err;
                err = FbTk::StringUtil::getStringBetween(str_label, 
                                                         line.c_str() + parse_pos, 
                                                         '{', '}');
                if (err>0) {
                    parse_pos += err;
                }
            } else
                continue; //read next line

            if (!str_key.size())
                continue; //read next line
            if (str_key == "Workspace") {
                unsigned int w;
                FB_istringstream iss(str_label.c_str());
                iss >> w;
                app.rememberWorkspace(w);
            } else if (str_key == "Layer") {
                unsigned int l;
                FB_istringstream iss(str_label.c_str());
                iss >> l;
                app.rememberLayer(l);
            } else if (str_key == "Dimensions") {
                unsigned int h,w;
                FB_istringstream iss(str_label.c_str());
                iss >> w >> h;
                app.rememberDimensions(w,h);
            } else if (str_key == "Position") {
                FB_istringstream iss;
                unsigned int r= 0;
                unsigned int x= 0;
                unsigned int y= 0;
                // more info about the parameter
                // in ::rememberPosition
                
                if ( str_option.length() )
                {
                  if      ( str_option == "UPPERLEFT"  ) r= POS_UPPERLEFT;
                  else if ( str_option == "UPPERRIGHT" ) r= POS_UPPERRIGHT;
                  else if ( str_option == "LOWERLEFT"  ) r= POS_LOWERLEFT;
                  else if ( str_option == "LOWERRIGHT" ) r= POS_LOWERRIGHT;
                  else if ( str_option == "CENTER" )     r= POS_CENTER;
                  else if ( str_option == "WINCENTER" )  r= POS_WINCENTER;
                  else {
                      iss.str(str_option);
                      iss >> r;
                  }
                }

                iss.str(str_label.c_str());
                iss >> x >> y;
                app.rememberPosition(x, y, r);
            } else if (str_key == "Shaded") {
                app.rememberShadedstate((str_label=="yes"));
            } else if (str_key == "Tab") {
                app.rememberTabstate((str_label=="yes"));
            } else if (str_key == "Deco") {
                if (str_label == "NONE") {
                    app.rememberDecostate((unsigned int) 0);
                } else if (str_label == "NORMAL") {
                    app.rememberDecostate((unsigned int) 0xfffffff);
                } else if (str_label == "TINY") {
                    app.rememberDecostate((unsigned int)
                                          FluxboxWindow::DECORM_TITLEBAR 
                                          | FluxboxWindow::DECORM_ICONIFY
                                          | FluxboxWindow::DECORM_MENU
                        );
                } else if (str_label == "TOOL") {
                    app.rememberDecostate((unsigned int)
                                          FluxboxWindow::DECORM_TITLEBAR
                                          | FluxboxWindow::DECORM_MENU
                        );
                } else if (str_label == "BORDER") {
                    app.rememberDecostate((unsigned int)
                                          FluxboxWindow::DECORM_BORDER
                                          | FluxboxWindow::DECORM_MENU
                        );
                } else {
                    unsigned int mask;
                    const char * str = str_label.c_str();
                    // it'll have at least one char and \0, so this is safe
                    FB_istringstream iss(str);
                    // check for hex
                    if (str[0] == '0' && str[1] == 'x') {
                        iss.seekg(2);
                        iss >> hex;
                    }
                    iss >> mask ;
                    app.rememberDecostate(mask);
                }
            } else if (str_key == "Sticky") {
                app.rememberStuckstate((str_label=="yes"));
            } else if (str_key == "Jump") {
                app.rememberJumpworkspace((str_label=="yes"));
            } else if (str_key == "Close") {
                app.rememberSaveOnClose((str_label=="yes"));
            } else if (str_key == "end") {
                return row;
            } else {
                cerr << "Unsupported apps key = " << str_key << endl;
            }
        }
    }
    return row;
}

void Remember::load() {

    string apps_string;
    Fluxbox::instance()->getDefaultDataFilename("apps", apps_string);

#ifdef DEBUG
    cerr<<__FILE__<<"("<<__FUNCTION__<<"): Loading apps file ["<<apps_string<<"]"<<endl;
#endif // DEBUG
    ifstream apps_file(apps_string.c_str());

    if (!apps_file.fail()) {
        if (!apps_file.eof()) {
            string line;
            int row = 0;
            bool in_group = false;
            std::list<ClientPattern *> grouped_pats;
            while (getline(apps_file, line) && ! apps_file.eof()) {
                row++;
                if (line[0] == '#')
                    continue;
                string key;
                int err=0;
                int pos = FbTk::StringUtil::getStringBetween(key, 
                                                             line.c_str(), 
                                                             '[', ']');

                if (pos > 0 && key == "app") {
                    ClientPattern *pat = new ClientPattern(line.c_str() + pos);
                    if (!in_group) {
                        if ((err = pat->error()) == 0) {
                            Application *app = new Application(false);
                            m_pats.push_back(make_pair(pat, app));
                            row += parseApp(apps_file, *app);
                        } else {
                            cerr<<"Error reading apps file at line "<<row<<", column "<<(err+pos)<<"."<<endl;
                            delete pat; // since it didn't work
                        }
                    } else {
                        grouped_pats.push_back(pat);
                    }
                } else if (pos > 0 && key == "startup") {
                    if (!handleStartupItem(line, pos)) {
                        cerr<<"Error reading apps file at line "<<row<<"."<<endl;
                    }
                    // save the item even if it was bad (aren't we nice)
                    m_startups.push_back(line.substr(pos));
                } else if (pos > 0 && key == "group") {
                    in_group = true;
                } else if (in_group) {
                    // otherwise assume that it is the start of the attributes
                    Application *app = new Application(true);
                    while (!grouped_pats.empty()) {
                        // associate all the patterns with this app
                        m_pats.push_back(make_pair(grouped_pats.front(), app));
                        grouped_pats.pop_front();
                    }
                    
                    // we hit end... probably don't have attribs for the group
                    // so finish it off with an empty application
                    // otherwise parse the app
                    if (!(pos>0 && key == "end")) {
                        row += parseApp(apps_file, *app, &line);
                    }
                    in_group = false;
                } else
                    cerr<<"Error in apps file on line "<<row<<"."<<endl;
                
            }
        } else {
#ifdef DEBUG
            cerr<<__FILE__<<"("<<__FUNCTION__<< ") Empty apps file" << endl;
#endif
        }
    } else {
        cerr << "apps file failure" << endl;
    }
}

void Remember::save() {
#ifdef DEBUG
    cerr<<__FILE__<<"("<<__FUNCTION__<<"): Saving apps file..."<<endl;
#endif // DEBUG
    string apps_string;
    Fluxbox::instance()->getDefaultDataFilename("apps", apps_string);
    ofstream apps_file(apps_string.c_str());

    // first of all we output all the startup commands
    Startups::iterator sit = m_startups.begin();
    Startups::iterator sit_end = m_startups.end();
    for (; sit != sit_end; ++sit) {
        apps_file<<"[startup] "<<(*sit)<<endl;
    }

    Patterns::iterator it = m_pats.begin();
    Patterns::iterator it_end = m_pats.end();

    std::set<Application *> grouped_apps; // no duplicates

    for (; it != it_end; ++it) {
        Application &a = *it->second;
        if (a.is_grouped) {
            // if already processed
            if (grouped_apps.find(&a) != grouped_apps.end())
                continue;
            grouped_apps.insert(&a);
            // otherwise output this whole group
            apps_file << "[group]" << endl;
            Patterns::iterator git = m_pats.begin();
            Patterns::iterator git_end = m_pats.end();
            for (; git != git_end; git++) {
                if (git->second == &a) {
                    apps_file << " [app]"<<git->first->toString()<<endl;
                }
            }
        } else {
            apps_file << "[app]"<<it->first->toString()<<endl;
        }
        if (a.workspace_remember) {
            apps_file << "  [Workspace]\t{" << a.workspace << "}" << endl;
        }
        if (a.dimensions_remember) {
            apps_file << "  [Dimensions]\t{" << a.w << " " << a.h << "}" << endl;
        }
        if (a.position_remember) {
            apps_file << "  [Position]\t{" << a.x << " " << a.y << "}" << endl;
        }
        if (a.shadedstate_remember) {
            apps_file << "  [Shaded]\t{" << ((a.shadedstate)?"yes":"no") << "}" << endl;
        }
        if (a.tabstate_remember) {
            apps_file << "  [Tab]\t\t{" << ((a.tabstate)?"yes":"no") << "}" << endl;
        }
        if (a.decostate_remember) {
            switch (a.decostate) {
            case (0) :
                apps_file << "  [Deco]\t{NONE}" << endl; 
                break;
            case (0xffffffff):
            case (FluxboxWindow::DECORM_LAST - 1):
                apps_file << "  [Deco]\t{NORMAL}" << endl;
                break;
            case (FluxboxWindow::DECORM_TITLEBAR 
                  | FluxboxWindow::DECORM_ICONIFY
                  | FluxboxWindow::DECORM_MENU):
                apps_file << "  [Deco]\t{TOOL}" << endl;
                break;
            case (FluxboxWindow::DECORM_TITLEBAR 
                  | FluxboxWindow::DECORM_MENU):
                apps_file << "  [Deco]\t{TINY}" << endl;
                break;
            case (FluxboxWindow::DECORM_BORDER 
                  | FluxboxWindow::DECORM_MENU):
                apps_file << "  [Deco]\t{BORDER}" << endl;
                break;
            default:
                apps_file << "  [Deco]\t{0x"<<hex<<a.decostate<<dec<<"}"<<endl;
                break;
            }
        }
        if (a.stuckstate_remember) {
            apps_file << "  [Sticky]\t{" << ((a.stuckstate)?"yes":"no") << "}" << endl;
        }
        if (a.jumpworkspace_remember) {
            apps_file << "  [Jump]\t{" << ((a.jumpworkspace)?"yes":"no") << "}" << endl;
        }
        if (a.layer_remember) {
            apps_file << "  [Layer]\t{" << a.layer << "}" << endl;
        }
        if (a.save_on_close_remember) {
            apps_file << "  [Close]\t{" << ((a.save_on_close)?"yes":"no") << "}" << endl;
        }
        apps_file << "[end]" << endl;
    }
}

bool Remember::isRemembered(WinClient &winclient, Attribute attrib) {
    Application *app = find(winclient);
    if (!app) return false;
    switch (attrib) {
    case REM_WORKSPACE:
        return app->workspace_remember;
        break;
    case REM_DIMENSIONS:
        return app->dimensions_remember;
        break;
    case REM_POSITION:
        return app->position_remember;
        break;
    case REM_STUCKSTATE:
        return app->stuckstate_remember;
        break;
    case REM_DECOSTATE:
        return app->decostate_remember;
        break;
    case REM_SHADEDSTATE:
        return app->shadedstate_remember;
        break;
        //    case REM_TABSTATE:
        //        return app->tabstate_remember;
        //        break;
    case REM_JUMPWORKSPACE:
        return app->jumpworkspace_remember;
        break;
    case REM_LAYER:
        return app->layer_remember;
        break;
    case REM_SAVEONCLOSE:
        return app->save_on_close_remember;
        break;
    case REM_LASTATTRIB:
    default:
        return false; // should never get here
    }
}

void Remember::rememberAttrib(WinClient &winclient, Attribute attrib) {
    FluxboxWindow *win = winclient.fbwindow();
    if (!win) return;
    Application *app = find(winclient);
    if (!app) {
        app = add(winclient);
        if (!app) return;
    }
    switch (attrib) {
    case REM_WORKSPACE:
        app->rememberWorkspace(win->workspaceNumber());
        break;
    case REM_DIMENSIONS:
        app->rememberDimensions(win->width(), win->height());
        break;
    case REM_POSITION:
        app->rememberPosition(win->x(), win->y());
        break;
    case REM_SHADEDSTATE:
        app->rememberShadedstate(win->isShaded());
        break;
    case REM_DECOSTATE:
        app->rememberDecostate(win->decorationMask());
        break;
    case REM_STUCKSTATE:
        app->rememberStuckstate(win->isStuck());
        break;
        //    case REM_TABSTATE:
        //        break;
    case REM_JUMPWORKSPACE:
        app->rememberJumpworkspace(true);
        break;
    case REM_LAYER:
        app->rememberLayer(win->layerNum());
        break;
    case REM_SAVEONCLOSE:
        app->rememberSaveOnClose(true);
        break;
    case REM_LASTATTRIB:
    default:
        // nothing
        break;
    }
}

void Remember::forgetAttrib(WinClient &winclient, Attribute attrib) {
    FluxboxWindow *win = winclient.fbwindow();
    if (!win) return;
    Application *app = find(winclient);
    if (!app) {
        app = add(winclient);
        if (!app) return;
    }
    switch (attrib) {
    case REM_WORKSPACE:
        app->forgetWorkspace();
        break;
    case REM_DIMENSIONS:
        app->forgetDimensions();
        break;
    case REM_POSITION:
        app->forgetPosition();
        break;
    case REM_STUCKSTATE:
        app->forgetStuckstate();
        break;
    case REM_DECOSTATE:
        app->forgetDecostate();
        break;
    case REM_SHADEDSTATE:
        app->forgetShadedstate();
        break;
//    case REM_TABSTATE:
//        break;
    case REM_JUMPWORKSPACE:
        app->forgetJumpworkspace();
        break;
    case REM_LAYER:
        app->forgetLayer();
        break;
    case REM_SAVEONCLOSE:
        app->forgetSaveOnClose();
        break;
    case REM_LASTATTRIB:
    default:
        // nothing
        break;
    }
}

void Remember::setupFrame(FluxboxWindow &win) {
    WinClient &winclient = win.winClient();

    // we don't touch the window if it is a transient
    // of something else

    // All windows get the remember menu.
    // TODO: nls
    win.addExtraMenu("Remember...", createRememberMenu(*this, win, (winclient.transientFor() == 0)));

    if (winclient.transientFor()) 
        return;

    Application *app = find(winclient);
    if (app == 0) 
        return; // nothing to do

    if (app->is_grouped && app->group == 0)
        app->group = &win;

    BScreen &screen = winclient.screen();

    if (app->workspace_remember) {
        // TODO: fix placement to initialise properly
        screen.reassociateWindow(&win, app->workspace, true); 
        if (app->jumpworkspace_remember)
            screen.changeWorkspaceID(app->workspace);
    }

    if (app->dimensions_remember)
        win.resize(app->w, app->h);
    
    if (app->position_remember) {
        switch (app->refc) {
          default:
          case POS_UPPERLEFT: // upperleft corner
        win.move(app->x, app->y);
              break;
          case POS_UPPERRIGHT: // upperright corner
              win.move(screen.width() - win.width() - app->x, app->y);
              break;
          case POS_LOWERLEFT: // lowerleft corner
              win.move(app->x, screen.height() - win.height() - app->y);
              break;
          case POS_LOWERRIGHT: // lowerright corner
              win.move(screen.width() - win.width() - app->x,
                       screen.height() - win.height() - app->y);
              break;
          case POS_CENTER: // center of the screen, windows topleft corner is on the center
                win.move((screen.width() / 2) + app->x,
                         (screen.height() / 2) + app->y);
              break;
          case POS_WINCENTER: // the window is centered REALLY upon the center
                win.move((screen.width() / 2) - ( win.width() / 2 ) + app->x,
                         (screen.height() / 2) - ( win.height() / 2 ) + app->y);
              break;
        };
    }

    if (app->shadedstate_remember)
        // if inconsistent...
        if (win.isShaded() && !app->shadedstate ||
            !win.isShaded() && app->shadedstate)
            win.shade(); // toggles

    // external tabs aren't available atm...
    //if (app->tabstate_remember) ...

    if (app->decostate_remember)
        win.setDecorationMask(app->decostate);

    if (app->stuckstate_remember)
        // if inconsistent...
        if (win.isStuck() && !app->stuckstate ||
            !win.isStuck() && app->stuckstate)
            win.stick(); // toggles

    if (app->layer_remember)
        win.moveToLayer(app->layer);

}

void Remember::setupClient(WinClient &winclient) {

    Application *app = find(winclient);
    if (app == 0) 
        return; // nothing to do

    if (winclient.fbwindow() == 0 && app->is_grouped && app->group) {
        app->group->attachClient(winclient);
    }
}

void Remember::updateClientClose(WinClient &winclient) {
    Application *app = find(winclient);

    if (app && (app->save_on_close_remember && app->save_on_close)) {

        for (int attrib = 0; attrib <= REM_LASTATTRIB; attrib++) {
            if (isRemembered(winclient, (Attribute) attrib)) {
                rememberAttrib(winclient, (Attribute) attrib);
            }
        }

        save();
    }

    // we need to get rid of references to this client
    Clients::iterator wc_it = m_clients.find(&winclient);

    if (wc_it != m_clients.end()) {
        m_clients.erase(wc_it);
    }

}

void Remember::updateFrameClose(FluxboxWindow &win) {
    // scan all applications and remove this fbw if it is a recorded group
    Patterns::iterator it = m_pats.begin();
    while (it != m_pats.end()) {
        if (&win == it->second->group)
            it->second->group = 0;
        ++it;
    }
}