fluxbox/src/IconbarTool.cc
2008-08-23 12:46:36 -07:00

577 lines
19 KiB
C++

// IconbarTool.cc
// Copyright (c) 2003 - 2006 Henrik Kinnunen (fluxgen at fluxbox dot org)
// 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.
#include "IconbarTool.hh"
#include "fluxbox.hh"
#include "WindowCmd.hh"
#include "Screen.hh"
#include "IconbarTheme.hh"
#include "Window.hh"
#include "IconButton.hh"
#include "Workspace.hh"
#include "FbMenu.hh"
#include "FbTk/CommandParser.hh"
#include "WinClient.hh"
#include "FocusControl.hh"
#include "FbCommands.hh"
#include "Layer.hh"
#include "FbTk/STLUtil.hh"
#include "FbTk/I18n.hh"
#include "FbTk/Menu.hh"
#include "FbTk/RadioMenuItem.hh"
#include "FbTk/BoolMenuItem.hh"
#include "FbTk/RefCount.hh"
#include "FbTk/SimpleCommand.hh"
#include "FbTk/ImageControl.hh"
#include "FbTk/MacroCommand.hh"
#include "FbTk/MenuSeparator.hh"
#include <typeinfo>
#include <iterator>
#ifdef HAVE_CSTRING
#include <cstring>
#else
#include <string.h>
#endif
using std::string;
using std::list;
#ifdef DEBUG
using std::cerr;
using std::endl;
#endif // DEBUG
namespace FbTk {
template<>
void FbTk::Resource<FbTk::Container::Alignment>::setDefaultValue() {
m_value = FbTk::Container::RELATIVE;
}
template<>
string FbTk::Resource<FbTk::Container::Alignment>::getString() const {
if (m_value == FbTk::Container::LEFT)
return string("Left");
if (m_value == FbTk::Container::RIGHT)
return string("Right");
return string("Relative");
}
template<>
void FbTk::Resource<FbTk::Container::Alignment>::setFromString(const char *str) {
if (strcasecmp(str, "Left") == 0)
m_value = FbTk::Container::LEFT;
else if (strcasecmp(str, "Right") == 0)
m_value = FbTk::Container::RIGHT;
else if (strcasecmp(str, "Relative") == 0)
m_value = FbTk::Container::RELATIVE;
else
setDefaultValue();
}
} // end namespace FbTk
namespace {
class ToolbarModeMenuItem : public FbTk::RadioMenuItem {
public:
ToolbarModeMenuItem(const FbTk::FbString &label, IconbarTool &handler,
string mode,
FbTk::RefCount<FbTk::Command<void> > &cmd):
FbTk::RadioMenuItem(label, cmd), m_handler(handler), m_mode(mode) {
setCloseOnClick(false);
}
bool isSelected() const { return m_handler.mode() == m_mode; }
void click(int button, int time, unsigned int mods) {
m_handler.setMode(m_mode);
FbTk::RadioMenuItem::click(button, time, mods);
}
private:
IconbarTool &m_handler;
string m_mode;
};
class ToolbarAlignMenuItem: public FbTk::RadioMenuItem {
public:
ToolbarAlignMenuItem(const FbTk::FbString &label, IconbarTool &handler,
FbTk::Container::Alignment mode,
FbTk::RefCount<FbTk::Command<void> > &cmd):
FbTk::RadioMenuItem(label, cmd), m_handler(handler), m_mode(mode) {
setCloseOnClick(false);
}
bool isSelected() const { return m_handler.alignment() == m_mode; }
void click(int button, int time, unsigned int mods) {
m_handler.setAlignment(m_mode);
FbTk::RadioMenuItem::click(button, time, mods);
}
private:
IconbarTool &m_handler;
FbTk::Container::Alignment m_mode;
};
void setupModeMenu(FbTk::Menu &menu, IconbarTool &handler) {
using namespace FbTk;
_FB_USES_NLS;
menu.setLabel(_FB_XTEXT(Toolbar, IconbarMode, "Iconbar Mode", "Menu title - chooses which set of icons are shown in the iconbar"));
RefCount<Command<void> > saverc_cmd(new FbCommands::SaveResources());
menu.insert(new ToolbarModeMenuItem(_FB_XTEXT(Toolbar, IconbarModeNone,
"None", "No icons are shown in the iconbar"),
handler,
"none", saverc_cmd));
menu.insert(new ToolbarModeMenuItem(
_FB_XTEXT(Toolbar, IconbarModeIcons,
"Icons", "Iconified windows from all workspaces are shown"),
handler,
"{static groups} (minimized=yes)", saverc_cmd));
menu.insert(new ToolbarModeMenuItem(
_FB_XTEXT(Toolbar, IconbarModeNoIcons,
"NoIcons", "No iconified windows from all workspaces are shown"),
handler,
"{static groups} (minimized=no)", saverc_cmd));
menu.insert(new ToolbarModeMenuItem(
_FB_XTEXT(Toolbar, IconbarModeWorkspaceIcons,
"WorkspaceIcons", "Iconified windows from this workspace are shown"),
handler,
"{static groups} (minimized=yes) (workspace)", saverc_cmd));
menu.insert(new ToolbarModeMenuItem(
_FB_XTEXT(Toolbar, IconbarModeWorkspaceNoIcons,
"WorkspaceNoIcons", "No iconified windows from this workspace are shown"),
handler,
"{static groups} (minimized=no) (workspace)", saverc_cmd));
menu.insert(new ToolbarModeMenuItem(
_FB_XTEXT(Toolbar, IconbarModeWorkspace,
"Workspace", "Normal and iconified windows from this workspace are shown"),
handler,
"{static groups} (workspace)", saverc_cmd));
menu.insert(new ToolbarModeMenuItem(
_FB_XTEXT(Toolbar, IconbarModeAllWindows, "All Windows", "All windows are shown"),
handler,
"{static groups}", saverc_cmd));
menu.insert(new FbTk::MenuSeparator());
menu.insert(new ToolbarAlignMenuItem(
_FB_XTEXT(Align, Left, "Left", "Align to the left"),
handler,
FbTk::Container::LEFT, saverc_cmd));
menu.insert(new ToolbarAlignMenuItem(
_FB_XTEXT(Align, Relative, "Relative", "Align relative to the width"),
handler,
FbTk::Container::RELATIVE, saverc_cmd));
menu.insert(new ToolbarAlignMenuItem(
_FB_XTEXT(Align, Right, "Right", "Align to the right"),
handler,
FbTk::Container::RIGHT, saverc_cmd));
menu.insert(new FbTk::MenuSeparator());
menu.updateMenu();
}
typedef FbTk::RefCount<FbTk::Command<void> > RefCmd;
class ShowMenu: public FbTk::Command<void> {
public:
explicit ShowMenu(FluxboxWindow &win):m_win(win) { }
void execute() {
// get last button pos
const XEvent &event = Fluxbox::instance()->lastEvent();
int x = event.xbutton.x_root - (m_win.menu().width() / 2);
int y = event.xbutton.y_root - (m_win.menu().height() / 2);
m_win.popupMenu(x, y);
}
private:
FluxboxWindow &m_win;
};
class FocusCommand: public FbTk::Command<void> {
public:
explicit FocusCommand(Focusable &win): m_win(win) { }
void execute() {
// this needs to be a local variable, as this object could be destroyed
// if the workspace is changed.
FluxboxWindow *fbwin = m_win.fbwindow();
if (!fbwin)
return;
if (m_win.isFocused())
fbwin->iconify();
else {
m_win.focus();
fbwin->raise();
}
}
private:
Focusable &m_win;
};
}; // end anonymous namespace
IconbarTool::IconbarTool(const FbTk::FbWindow &parent, IconbarTheme &theme,
FbTk::ThemeProxy<IconbarTheme> &focused_theme,
FbTk::ThemeProxy<IconbarTheme> &unfocused_theme,
BScreen &screen, FbTk::Menu &menu):
ToolbarItem(ToolbarItem::RELATIVE),
m_screen(screen),
m_icon_container(parent),
m_theme(theme),
m_focused_theme(focused_theme),
m_unfocused_theme(unfocused_theme),
m_empty_pm( screen.imageControl() ),
m_winlist(new FocusableList(screen)),
m_mode("none"),
m_rc_mode(screen.resourceManager(), "{static groups} (workspace)",
screen.name() + ".iconbar.mode", screen.altName() + ".Iconbar.Mode"),
m_rc_alignment(screen.resourceManager(), FbTk::Container::RELATIVE,
screen.name() + ".iconbar.alignment", screen.altName() + ".Iconbar.Alignment"),
m_rc_client_width(screen.resourceManager(), 70,
screen.name() + ".iconbar.iconWidth", screen.altName() + ".Iconbar.IconWidth"),
m_rc_client_padding(screen.resourceManager(), 10,
screen.name() + ".iconbar.iconTextPadding", screen.altName() + ".Iconbar.IconTextPadding"),
m_rc_use_pixmap(screen.resourceManager(), true,
screen.name() + ".iconbar.usePixmap", screen.altName() + ".Iconbar.UsePixmap"),
m_menu(screen.menuTheme(), screen.imageControl(),
*screen.layerManager().getLayer(Layer::MENU)) {
// setup mode menu
setupModeMenu(m_menu, *this);
_FB_USES_NLS;
using namespace FbTk;
// setup use pixmap item to reconfig iconbar and save resource on click
MacroCommand *save_and_reconfig = new MacroCommand();
RefCount<Command<void> > reconfig(new SimpleCommand<IconbarTool>(*this, &IconbarTool::renderTheme));
RefCount<Command<void> > save(FbTk::CommandParser<void>::instance().parse("saverc"));
save_and_reconfig->add(reconfig);
save_and_reconfig->add(save);
RefCount<Command<void> > s_and_reconfig(save_and_reconfig);
m_menu.insert(new FbTk::BoolMenuItem(_FB_XTEXT(Toolbar, ShowIcons,
"Show Pictures", "chooses if little icons are shown next to title in the iconbar"),
m_rc_use_pixmap, s_and_reconfig));
m_menu.updateMenu();
// must be internal menu, otherwise toolbar main menu tries to delete it.
m_menu.setInternalMenu();
// add iconbar menu to toolbar menu
menu.insert(m_menu.label(), &m_menu);
// setup signals
theme.reconfigSig().attach(this);
focused_theme.reconfigSig().attach(this);
unfocused_theme.reconfigSig().attach(this);
setMode(*m_rc_mode);
}
IconbarTool::~IconbarTool() {
deleteIcons();
}
void IconbarTool::move(int x, int y) {
m_icon_container.move(x, y);
}
void IconbarTool::resize(unsigned int width, unsigned int height) {
m_icon_container.resize(width, height);
renderTheme();
}
void IconbarTool::moveResize(int x, int y,
unsigned int width, unsigned int height) {
m_icon_container.moveResize(x, y, width, height);
renderTheme();
}
void IconbarTool::show() {
m_icon_container.show();
}
void IconbarTool::hide() {
m_icon_container.hide();
}
void IconbarTool::setAlignment(FbTk::Container::Alignment align) {
*m_rc_alignment = align;
update(0);
m_menu.reconfigure();
}
void IconbarTool::setMode(string mode) {
if (mode == m_mode)
return;
*m_rc_mode = m_mode = mode;
// lock graphics update
m_icon_container.setUpdateLock(true);
if (m_winlist.get()) {
m_winlist->addSig().detach(this);
m_winlist->removeSig().detach(this);
m_winlist->orderSig().detach(this);
m_winlist->resetSig().detach(this);
}
if (mode == "none")
m_winlist.reset(new FocusableList(m_screen));
else
m_winlist.reset(new FocusableList(m_screen,
mode + " (iconhidden=no)"));
if (m_winlist.get()) {
m_winlist->addSig().attach(this);
m_winlist->removeSig().attach(this);
m_winlist->orderSig().attach(this);
m_winlist->resetSig().attach(this);
}
reset();
// unlock graphics update
m_icon_container.setUpdateLock(false);
m_icon_container.update();
m_icon_container.showSubwindows();
renderTheme();
m_menu.reconfigure();
}
unsigned int IconbarTool::width() const {
return m_icon_container.width();
}
unsigned int IconbarTool::height() const {
return m_icon_container.height();
}
unsigned int IconbarTool::borderWidth() const {
return m_icon_container.borderWidth();
}
void IconbarTool::update(FbTk::Subject *subj) {
// ignore updates if we're shutting down
if (m_screen.isShuttingdown()) {
if (!m_icons.empty())
deleteIcons();
return;
}
m_icon_container.setAlignment(*m_rc_alignment);
// clamp to normal values
if (*m_rc_client_width < 1)
*m_rc_client_width = 10;
else if (*m_rc_client_width > 400)
*m_rc_client_width = 400;
m_icon_container.setMaxSizePerClient(*m_rc_client_width);
if (subj == &m_focused_theme.reconfigSig() ||
subj == &m_unfocused_theme.reconfigSig() ||
subj == &m_theme.reconfigSig()) {
setMode(*m_rc_mode);
return;
}
// lock graphic update
m_icon_container.setUpdateLock(true);
if (subj && typeid(*subj) == typeid(FocusableList::FocusableListSubject)) {
FocusableList::FocusableListSubject *fsubj =
static_cast<FocusableList::FocusableListSubject *>(subj);
if (subj == &m_winlist->addSig())
insertWindow(*fsubj->win());
else if (subj == &m_winlist->removeSig())
removeWindow(*fsubj->win());
else if (subj == &m_winlist->resetSig())
reset();
else if (subj == &m_winlist->orderSig())
insertWindow(*fsubj->win());
}
// unlock container and update graphics
m_icon_container.setUpdateLock(false);
m_icon_container.update();
m_icon_container.showSubwindows();
// another renderTheme we hopefully shouldn't need? These renders
// should be done individually above
// nope, we still need it (or at least I'm not bothering to fix it yet)
// a new IconButton doesn't get resized properly until the
// m_icon_container.update() above; then, it never runs drawText() again,
// so text can end up behind program icons
renderTheme();
}
void IconbarTool::insertWindow(Focusable &win, int pos) {
IconButton *button = 0;
IconMap::iterator icon_it = m_icons.find(&win);
if (icon_it != m_icons.end())
button = icon_it->second;
if (button)
m_icon_container.removeItem(button);
else
button = makeButton(win);
if (!button) return;
if (pos == -2) {
pos = 0;
list<Focusable *>::iterator it = m_winlist->clientList().begin(),
it_end = m_winlist->clientList().end();
for (; it != it_end && *it != &win; ++it)
pos++;
}
m_icon_container.insertItem(button, pos);
}
void IconbarTool::reset() {
deleteIcons();
updateList();
}
void IconbarTool::updateSizing() {
m_icon_container.setBorderWidth(m_theme.border().width());
m_icon_container.setBorderColor(m_theme.border().color());
IconMap::iterator icon_it = m_icons.begin();
const IconMap::iterator icon_it_end = m_icons.end();
for (; icon_it != icon_it_end; ++icon_it)
icon_it->second->reconfigTheme();
}
void IconbarTool::renderTheme(unsigned char alpha) {
m_alpha = alpha;
renderTheme();
}
void IconbarTool::renderTheme() {
// update button sizes before we get max width per client!
updateSizing();
// if we dont have any icons then we should render empty texture
if (!m_theme.emptyTexture().usePixmap()) {
m_empty_pm.reset( 0 );
m_icon_container.setBackgroundColor(m_theme.emptyTexture().color());
} else {
m_empty_pm.reset(m_screen.imageControl().
renderImage(m_icon_container.width(),
m_icon_container.height(),
m_theme.emptyTexture(), orientation()));
m_icon_container.setBackgroundPixmap(m_empty_pm);
}
m_icon_container.setAlpha(m_alpha);
// update buttons
IconMap::iterator icon_it = m_icons.begin();
const IconMap::iterator icon_it_end = m_icons.end();
for (; icon_it != icon_it_end; ++icon_it)
renderButton(*icon_it->second);
}
void IconbarTool::renderButton(IconButton &button, bool clear) {
button.setPixmap(*m_rc_use_pixmap);
button.setTextPadding(*m_rc_client_padding);
button.reconfigTheme();
if (clear)
button.clear(); // the clear also updates transparent
}
void IconbarTool::deleteIcons() {
m_icon_container.removeAll();
FbTk::STLUtil::destroyAndClearSecond(m_icons);
}
void IconbarTool::removeWindow(Focusable &win) {
// got window die signal, lets find and remove the window
IconMap::iterator it = m_icons.find(&win);
if (it == m_icons.end())
return;
#ifdef DEBUG
cerr<<"IconbarTool::"<<__FUNCTION__<<"( 0x"<<&win<<" title = "<<win.title()<<") found!"<<endl;
#endif // DEBUG
// remove from list and render theme again
IconButton *button = it->second;
m_icons.erase(it);
m_icon_container.removeItem(button);
delete button;
}
IconButton *IconbarTool::makeButton(Focusable &win) {
// we just want windows that have clients
FluxboxWindow *fbwin = win.fbwindow();
if (!fbwin || fbwin->clientList().empty())
return 0;
#ifdef DEBUG
cerr<<"IconbarTool::addWindow(0x"<<&win<<" title = "<<win.title()<<")"<<endl;
#endif // DEBUG
IconButton *button = new IconButton(m_icon_container, m_focused_theme,
m_unfocused_theme, win);
RefCmd focus_cmd(new ::FocusCommand(win));
RefCmd menu_cmd(new ::ShowMenu(*fbwin));
button->setOnClick(focus_cmd, 1);
button->setOnClick(menu_cmd, 3);
renderButton(*button, false); // update the attributes, but don't clear it
m_icons[&win] = button;
return button;
}
void IconbarTool::updateList() {
list<Focusable *>::iterator it = m_winlist->clientList().begin();
list<Focusable *>::iterator it_end = m_winlist->clientList().end();
for (; it != it_end; ++it) {
if ((*it)->fbwindow())
insertWindow(**it, -1);
}
renderTheme();
}
void IconbarTool::setOrientation(FbTk::Orientation orient) {
m_icon_container.setOrientation(orient);
ToolbarItem::setOrientation(orient);
}