fix up theming of the toolbar
This commit is contained in:
parent
f32969d4e5
commit
5337fc6640
13 changed files with 128 additions and 60 deletions
|
@ -1,7 +1,14 @@
|
|||
(Format: Year/Month/Day)
|
||||
Changes for 0.9.10:
|
||||
*04/08/26:
|
||||
* Re-implement bevels in toolbar, plus numerous toolbar-related theme
|
||||
fixes => old styles now look like they used to! (Simon)
|
||||
Toolbar.cc ToolbarItem.h ToolTheme.cc ToolbarTheme.cc ToolFactory.cc
|
||||
ArrowButton.cc ButtonTheme.cc ClockTool.cc IconbarTheme.cc
|
||||
IconbarTool.cc SystemTray.hh WorkspaceNameTool.cc
|
||||
*04/08/25:
|
||||
* Fix to draw Cursors in Textboxes correctly again (Mathias)
|
||||
* Fix to draw Cursors in Textboxes correctly again (Mathias)
|
||||
Font.cc
|
||||
*04/08/22:
|
||||
* Preliminary basic support for icons in fluxbox-generate_menu (Han)
|
||||
- put a <progname>.xpm into ~/.fluxbox/icons. e.g. Eterm.xpm
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: ArrowButton.cc,v 1.6 2004/05/18 08:35:22 grubert Exp $
|
||||
// $Id: ArrowButton.cc,v 1.7 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#include "ArrowButton.hh"
|
||||
|
||||
|
@ -84,9 +84,9 @@ void ArrowButton::drawArrow() {
|
|||
XPoint pts[3];
|
||||
unsigned int w = width();
|
||||
unsigned int h = height();
|
||||
// arrow size: half of the button
|
||||
unsigned int ax = w / 2;
|
||||
unsigned int ay = h / 2;
|
||||
// arrow size: half of the button
|
||||
unsigned int ax = w / 2;
|
||||
unsigned int ay = h / 2;
|
||||
switch (m_arrow_type) {
|
||||
case LEFT:
|
||||
// start at the tip
|
||||
|
|
|
@ -14,6 +14,7 @@ ButtonTheme::ButtonTheme(int screen_num,
|
|||
|
||||
bool ButtonTheme::fallback(FbTk::ThemeItem_base &item) {
|
||||
|
||||
/* Don't fallback these for theme backwards compatibility
|
||||
if (item.name().find(".borderWidth") != std::string::npos) {
|
||||
return FbTk::ThemeManager::instance().loadItem(item, "borderWidth", "BorderWidth");
|
||||
}
|
||||
|
@ -21,7 +22,7 @@ bool ButtonTheme::fallback(FbTk::ThemeItem_base &item) {
|
|||
if (item.name().find(".borderColor") != std::string::npos) {
|
||||
return FbTk::ThemeManager::instance().loadItem(item, "borderColor", "BorderColor");
|
||||
}
|
||||
|
||||
*/
|
||||
if (item.name().find(".pressed") != std::string::npos) {
|
||||
// copy texture
|
||||
*m_pressed_texture = texture();
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: ClockTool.cc,v 1.11 2004/06/16 15:38:19 rathnor Exp $
|
||||
// $Id: ClockTool.cc,v 1.12 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#include "ClockTool.hh"
|
||||
|
||||
|
@ -204,7 +204,11 @@ void ClockTool::update(FbTk::Subject *subj) {
|
|||
for (size_t i=0; i<m_button.text().size() + 2; ++i)
|
||||
text += '0';
|
||||
|
||||
resize(m_theme.font().textWidth(text.c_str(), text.size()), m_button.height());
|
||||
int new_width = m_theme.font().textWidth(text.c_str(), text.size());
|
||||
if (new_width != m_button.width()) {
|
||||
resize(m_theme.font().textWidth(text.c_str(), text.size()), m_button.height());
|
||||
resizeSig().notify();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int ClockTool::borderWidth() const {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: IconbarTheme.cc,v 1.10 2004/05/24 15:30:52 rathnor Exp $
|
||||
// $Id: IconbarTheme.cc,v 1.11 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#include "IconbarTheme.hh"
|
||||
#include "FbTk/App.hh"
|
||||
|
@ -75,14 +75,12 @@ bool IconbarTheme::fallback(FbTk::ThemeItem_base &item) {
|
|||
tm.loadItem(item, "toolbar.windowLabel", "toolbar.windowLabel") ||
|
||||
tm.loadItem(item, "toolbar", "toolbar")
|
||||
);
|
||||
} else if (item.name() == m_name + ".borderWidth" ||
|
||||
item.name() == m_name + ".focused.borderWidth" ||
|
||||
} else if (item.name() == m_name + ".focused.borderWidth" ||
|
||||
item.name() == m_name + ".unfocused.borderWidth")
|
||||
|
||||
// don't fallback for base border, for theme backwards compatibility
|
||||
return tm.loadItem(item, "borderWidth", "BorderWidth");
|
||||
|
||||
else if (item.name() == m_name + ".borderColor" ||
|
||||
item.name() == m_name + ".focused.borderColor" ||
|
||||
else if (item.name() == m_name + ".focused.borderColor" ||
|
||||
item.name() == m_name + ".unfocused.borderColor")
|
||||
|
||||
return tm.loadItem(item, "borderColor", "BorderColor");
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: IconbarTool.cc,v 1.41 2004/06/27 13:45:20 fluxgen Exp $
|
||||
// $Id: IconbarTool.cc,v 1.42 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#include "IconbarTool.hh"
|
||||
|
||||
|
@ -611,7 +611,8 @@ void IconbarTool::renderTheme() {
|
|||
m_screen.imageControl().removeImage(tmp);
|
||||
|
||||
// set to zero so its consistent and not ugly
|
||||
m_icon_container.setBorderWidth(0);
|
||||
m_icon_container.setBorderWidth(m_theme.border().width());
|
||||
m_icon_container.setBorderColor(m_theme.border().color());
|
||||
m_icon_container.setAlpha(m_theme.alpha());
|
||||
|
||||
// update buttons
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: SystemTray.hh,v 1.4 2004/04/19 22:48:19 fluxgen Exp $
|
||||
// $Id: SystemTray.hh,v 1.5 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#ifndef SYSTEMTRAY_HH
|
||||
#define SYSTEMTRAY_HH
|
||||
|
@ -65,11 +65,12 @@ public:
|
|||
int numClients() const { return m_clients.size(); }
|
||||
const FbTk::FbWindow &window() const { return m_window; }
|
||||
|
||||
inline void renderTheme() {}
|
||||
|
||||
private:
|
||||
typedef std::list<FbTk::FbWindow *> ClientList;
|
||||
ClientList::iterator findClient(Window win);
|
||||
|
||||
void renderTheme();
|
||||
void rearrangeClients();
|
||||
void removeAllClients();
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: ToolFactory.cc,v 1.4 2004/05/18 08:35:22 grubert Exp $
|
||||
// $Id: ToolFactory.cc,v 1.5 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#include "ToolFactory.hh"
|
||||
|
||||
|
@ -117,7 +117,7 @@ ToolbarItem *ToolFactory::create(const std::string &name, const FbTk::FbWindow &
|
|||
0, 0,
|
||||
button_size, button_size);
|
||||
win->setOnClick(cmd);
|
||||
return new ButtonTool(win, ToolbarItem::FIXED,
|
||||
return new ButtonTool(win, ToolbarItem::SQUARE,
|
||||
dynamic_cast<ButtonTheme &>(*m_button_theme),
|
||||
screen().imageControl());
|
||||
|
||||
|
@ -136,7 +136,7 @@ ToolbarItem *ToolFactory::create(const std::string &name, const FbTk::FbWindow &
|
|||
0, 0,
|
||||
button_size, button_size);
|
||||
win->setOnClick(cmd);
|
||||
return new ButtonTool(win, ToolbarItem::FIXED,
|
||||
return new ButtonTool(win, ToolbarItem::SQUARE,
|
||||
dynamic_cast<ButtonTheme &>(*m_button_theme),
|
||||
screen().imageControl());
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: ToolTheme.cc,v 1.6 2004/05/24 15:30:52 rathnor Exp $
|
||||
// $Id: ToolTheme.cc,v 1.7 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#include "ToolTheme.hh"
|
||||
|
||||
|
@ -43,6 +43,13 @@ void ToolTheme::reconfigTheme() {
|
|||
}
|
||||
|
||||
bool ToolTheme::fallback(FbTk::ThemeItem_base &item) {
|
||||
/* Don't fallback these for theme backwards compatibility
|
||||
if (item.name().find(".borderWidth") != std::string::npos) {
|
||||
return FbTk::ThemeManager::instance().loadItem(item, "borderWidth", "BorderWidth");
|
||||
} else if (item.name().find(".borderColor") != std::string::npos) {
|
||||
return FbTk::ThemeManager::instance().loadItem(item, "borderColor", "BorderColor");
|
||||
} else
|
||||
*/
|
||||
if (item.name().find(".justify") != std::string::npos) {
|
||||
return FbTk::ThemeManager::instance().loadItem(item,
|
||||
"toolbar.justify",
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: Toolbar.cc,v 1.147 2004/07/14 23:39:29 fluxgen Exp $
|
||||
// $Id: Toolbar.cc,v 1.148 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#include "Toolbar.hh"
|
||||
|
||||
|
@ -63,11 +63,8 @@
|
|||
#include <X11/keysym.h>
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <iterator>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
template<>
|
||||
|
@ -248,9 +245,6 @@ Toolbar::Toolbar(BScreen &scrn, FbTk::XLayer &layer, size_t width):
|
|||
frame.bevel_w = 1;
|
||||
frame.grab_x = frame.grab_y = 0;
|
||||
|
||||
// set antialias on themes
|
||||
m_tool_factory.updateThemes();
|
||||
|
||||
// setup hide timer
|
||||
m_hide_timer.setTimeout(Fluxbox::instance()->getAutoRaiseDelay());
|
||||
FbTk::RefCount<FbTk::Command> toggle_hidden(new FbTk::SimpleCommand<Toolbar>(*this, &Toolbar::toggleHidden));
|
||||
|
@ -457,6 +451,11 @@ void Toolbar::reconfigure() {
|
|||
if (theme().shape() && m_shape.get())
|
||||
m_shape->update();
|
||||
|
||||
ItemList::iterator item_it = m_item_list.begin();
|
||||
ItemList::iterator item_it_end = m_item_list.end();
|
||||
for (item_it = m_item_list.begin(); item_it != item_it_end; ++item_it) {
|
||||
(*item_it)->renderTheme();
|
||||
}
|
||||
|
||||
rearrangeItems();
|
||||
|
||||
|
@ -554,12 +553,14 @@ void Toolbar::exposeEvent(XExposeEvent &ee) {
|
|||
|
||||
void Toolbar::handleEvent(XEvent &event) {
|
||||
/* Commented out by Simon 16jun04, since it causes LOTS of rearrangeItems
|
||||
particularly on startup. Can't figure out why this is needed.
|
||||
particularly on startup. This was needed to resize when tool changes its own
|
||||
size, but it has too many side effects. Use the resizeSig in ToolbarItem instead.
|
||||
|
||||
if (event.type == ConfigureNotify &&
|
||||
event.xconfigure.window != window().window()) {
|
||||
rearrangeItems();
|
||||
}
|
||||
*/
|
||||
*/
|
||||
}
|
||||
|
||||
void Toolbar::update(FbTk::Subject *subj) {
|
||||
|
@ -870,16 +871,24 @@ void Toolbar::saveOnHead(int head) {
|
|||
reconfigure();
|
||||
}
|
||||
|
||||
/*
|
||||
* Place items next to each other, with a bevel width between,
|
||||
* above and below each item. BUT, if there is no bevel width, then
|
||||
* borders should be merged for evenness.
|
||||
*/
|
||||
|
||||
void Toolbar::rearrangeItems() {
|
||||
if (m_resize_lock || screen().isShuttingdown() ||
|
||||
m_item_list.empty())
|
||||
return;
|
||||
|
||||
// lock this
|
||||
m_resize_lock = true;
|
||||
// calculate size for fixed items
|
||||
ItemList::iterator item_it = m_item_list.begin();
|
||||
ItemList::iterator item_it_end = m_item_list.end();
|
||||
int fixed_width = 0; // combined size of all fixed items
|
||||
int bevel_width = theme().bevelWidth();
|
||||
int fixed_width = bevel_width; // combined size of all fixed items
|
||||
int fixed_items = 0; // number of fixed items
|
||||
int relative_items = 0;
|
||||
int last_bw = 0; // we show the largest border of adjoining items
|
||||
|
@ -888,20 +897,32 @@ void Toolbar::rearrangeItems() {
|
|||
if (!(*item_it)->active())
|
||||
continue;
|
||||
|
||||
if (!first) {
|
||||
if ((*item_it)->borderWidth() > last_bw)
|
||||
fixed_width += (*item_it)->borderWidth();
|
||||
else
|
||||
fixed_width += last_bw;
|
||||
} else
|
||||
first = false;
|
||||
int borderW = (*item_it)->borderWidth();
|
||||
|
||||
last_bw = (*item_it)->borderWidth();
|
||||
if (bevel_width > 0) {
|
||||
// the bevel and border are fixed whether relative or not
|
||||
fixed_width += bevel_width + 2*borderW;
|
||||
} else {
|
||||
if (!first) {
|
||||
if (borderW > last_bw)
|
||||
fixed_width += borderW;
|
||||
else
|
||||
fixed_width += last_bw;
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
}
|
||||
|
||||
last_bw = borderW;
|
||||
|
||||
if ((*item_it)->type() == ToolbarItem::FIXED) {
|
||||
fixed_width += (*item_it)->width();
|
||||
fixed_items++;
|
||||
} else if ((*item_it)->type() == ToolbarItem::RELATIVE) {
|
||||
} else if ((*item_it)->type() == ToolbarItem::SQUARE) {
|
||||
fixed_width += height() - 2*bevel_width;
|
||||
if (bevel_width != 0) fixed_width -= 2*borderW;
|
||||
fixed_items++;
|
||||
} else {
|
||||
relative_items++;
|
||||
}
|
||||
}
|
||||
|
@ -916,26 +937,36 @@ void Toolbar::rearrangeItems() {
|
|||
relative_width = 0;
|
||||
else { // size left after fixed items / number of relative items
|
||||
relative_width = (width() - fixed_width)/relative_items;
|
||||
rounding_error = width() - fixed_width - relative_items*relative_width;
|
||||
rounding_error = width() - fixed_width - relative_items*(relative_width);
|
||||
}
|
||||
}
|
||||
|
||||
// now move and resize the items
|
||||
// borderWidth added back on straight away
|
||||
int next_x = -m_item_list.front()->borderWidth(); // list isn't empty
|
||||
if (bevel_width != 0)
|
||||
next_x = 0;
|
||||
|
||||
last_bw = 0;
|
||||
for (item_it = m_item_list.begin(); item_it != item_it_end; ++item_it) {
|
||||
int borderW = (*item_it)->borderWidth();
|
||||
if (!(*item_it)->active()) {
|
||||
(*item_it)->hide();
|
||||
// make sure it still gets told the toolbar height
|
||||
(*item_it)->resize(1, height()); // width of 0 changes to 1 anyway
|
||||
(*item_it)->resize(1, height()-2*(bevel_width+borderW)); // width of 0 changes to 1 anyway
|
||||
continue;
|
||||
}
|
||||
int borderW = (*item_it)->borderWidth();
|
||||
int offset = bevel_width;
|
||||
int size_offset = 2*(borderW + bevel_width);
|
||||
|
||||
if (borderW > last_bw)
|
||||
next_x += borderW;
|
||||
else
|
||||
next_x += last_bw;
|
||||
if (bevel_width == 0) {
|
||||
offset = -borderW;
|
||||
size_offset = 0;
|
||||
if (borderW > last_bw)
|
||||
next_x += borderW;
|
||||
else
|
||||
next_x += last_bw;
|
||||
}
|
||||
last_bw = borderW;
|
||||
|
||||
if ((*item_it)->type() == ToolbarItem::RELATIVE) {
|
||||
|
@ -944,14 +975,19 @@ void Toolbar::rearrangeItems() {
|
|||
extra = 1;
|
||||
--rounding_error;
|
||||
}
|
||||
|
||||
(*item_it)->moveResize(next_x - borderW, -borderW, extra + relative_width, height());
|
||||
(*item_it)->moveResize(next_x + offset, offset, extra + relative_width, height() - size_offset);
|
||||
} else if ((*item_it)->type() == ToolbarItem::SQUARE) {
|
||||
(*item_it)->moveResize(next_x + offset, offset,
|
||||
height() - size_offset, height() - size_offset);
|
||||
} else { // fixed size
|
||||
(*item_it)->moveResize(next_x - borderW, -borderW,
|
||||
(*item_it)->width(), height());
|
||||
(*item_it)->moveResize(next_x + offset, offset,
|
||||
(*item_it)->width(), height() - size_offset);
|
||||
}
|
||||
(*item_it)->show();
|
||||
next_x += (*item_it)->width();
|
||||
next_x += (*item_it)->width() + bevel_width;
|
||||
if (bevel_width != 0)
|
||||
next_x += 2*borderW;
|
||||
|
||||
}
|
||||
// unlock
|
||||
m_resize_lock = false;
|
||||
|
|
|
@ -20,20 +20,21 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: ToolbarItem.hh,v 1.4 2004/06/20 10:29:51 rathnor Exp $
|
||||
// $Id: ToolbarItem.hh,v 1.5 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#ifndef TOOLBARITEM_HH
|
||||
#define TOOLBARITEM_HH
|
||||
|
||||
#include "FbTk/Subject.hh"
|
||||
|
||||
/// An item in the toolbar that has either fixed or realive size to the toolbar
|
||||
/// An item in the toolbar that has either fixed or relative size to the toolbar
|
||||
class ToolbarItem {
|
||||
public:
|
||||
/// size type in the toolbar
|
||||
enum Type {
|
||||
FIXED, ///< the size can not be changed
|
||||
RELATIVE ///< the size can be changed
|
||||
RELATIVE, ///< the size can be changed
|
||||
SQUARE ///< the size is fixed relative to the parent, and in both dimensions
|
||||
};
|
||||
|
||||
explicit ToolbarItem(Type type);
|
||||
|
@ -52,6 +53,10 @@ public:
|
|||
// some items might be there, but effectively empty, so shouldn't appear
|
||||
virtual bool active() { return true; }
|
||||
|
||||
// Tools should NOT listen to theme changes - they'll get notified by
|
||||
// the toolbar instead. Otherwise there are ordering problems.
|
||||
virtual void renderTheme() = 0;
|
||||
|
||||
FbTk::Subject &resizeSig() { return m_resize_sig; }
|
||||
|
||||
void setType(Type type) { m_type = type; }
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: ToolbarTheme.cc,v 1.15 2004/05/24 15:30:52 rathnor Exp $
|
||||
// $Id: ToolbarTheme.cc,v 1.16 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#include "ToolbarTheme.hh"
|
||||
|
||||
|
@ -65,9 +65,15 @@ ToolbarTheme::~ToolbarTheme() {
|
|||
}
|
||||
|
||||
bool ToolbarTheme::fallback(FbTk::ThemeItem_base &item) {
|
||||
if (item.name() == "toolbar.alpha") {
|
||||
if (item.name().find(".borderWidth") != std::string::npos) {
|
||||
return FbTk::ThemeManager::instance().loadItem(item, "borderWidth", "BorderWidth");
|
||||
} else if (item.name().find(".borderColor") != std::string::npos) {
|
||||
return FbTk::ThemeManager::instance().loadItem(item, "borderColor", "BorderColor");
|
||||
} else if (item.name() == "toolbar.alpha") {
|
||||
*m_alpha = 255;
|
||||
return true;
|
||||
} else if (item.name() == "toolbar.bevelWidth") {
|
||||
return FbTk::ThemeManager::instance().loadItem(item, "bevelWidth", "BevelWidth");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
// $Id: WorkspaceNameTool.cc,v 1.8 2004/06/16 15:38:19 rathnor Exp $
|
||||
// $Id: WorkspaceNameTool.cc,v 1.9 2004/08/25 17:16:40 rathnor Exp $
|
||||
|
||||
#include "WorkspaceNameTool.hh"
|
||||
|
||||
|
@ -70,8 +70,10 @@ void WorkspaceNameTool::moveResize(int x, int y,
|
|||
|
||||
void WorkspaceNameTool::update(FbTk::Subject *subj) {
|
||||
m_button.setText(m_screen.currentWorkspace()->name());
|
||||
if (m_button.width() != width())
|
||||
if (m_button.width() != width()) {
|
||||
resize(width(), height());
|
||||
resizeSig().notify();
|
||||
}
|
||||
renderTheme();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue