mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-24 01:54:22 +00:00
More iOS touch fixes. Tweaks to AudioProcessorGraph and PopupMenu, ComponentPeer.
This commit is contained in:
parent
4310106c58
commit
97f8de4323
11 changed files with 114 additions and 45 deletions
|
|
@ -37536,8 +37536,7 @@ AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeI
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor,
|
||||
uint32 nodeId)
|
||||
AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor, uint32 nodeId)
|
||||
{
|
||||
if (newProcessor == nullptr)
|
||||
{
|
||||
|
|
@ -37554,9 +37553,10 @@ AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const n
|
|||
// you can't add a node with an id that already exists in the graph..
|
||||
jassert (getNodeForId (nodeId) == nullptr);
|
||||
removeNode (nodeId);
|
||||
}
|
||||
|
||||
lastNodeId = nodeId;
|
||||
if (nodeId > lastNodeId)
|
||||
lastNodeId = nodeId;
|
||||
}
|
||||
|
||||
Node* const n = new Node (nodeId, newProcessor);
|
||||
nodes.add (n);
|
||||
|
|
@ -40943,13 +40943,13 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo)
|
|||
{
|
||||
WeakReference<Component> safePointer (this);
|
||||
|
||||
#if JUCE_LINUX
|
||||
#if JUCE_LINUX
|
||||
// it's wise to give the component a non-zero size before
|
||||
// putting it on the desktop, as X windows get confused by this, and
|
||||
// a (1, 1) minimum size is enforced here.
|
||||
setSize (jmax (1, getWidth()),
|
||||
jmax (1, getHeight()));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const Point<int> topLeft (getScreenPosition());
|
||||
|
||||
|
|
@ -63127,7 +63127,7 @@ BEGIN_JUCE_NAMESPACE
|
|||
|
||||
ComponentMovementWatcher::ComponentMovementWatcher (Component* const component_)
|
||||
: component (component_),
|
||||
lastPeer (nullptr),
|
||||
lastPeerID (0),
|
||||
reentrant (false),
|
||||
wasShowing (component_->isShowing())
|
||||
{
|
||||
|
|
@ -63153,15 +63153,16 @@ void ComponentMovementWatcher::componentParentHierarchyChanged (Component&)
|
|||
const ScopedValueSetter<bool> setter (reentrant, true);
|
||||
|
||||
ComponentPeer* const peer = component->getPeer();
|
||||
const uint32 peerID = peer != nullptr ? peer->getUniqueID() : 0;
|
||||
|
||||
if (peer != lastPeer)
|
||||
if (peerID != lastPeerID)
|
||||
{
|
||||
componentPeerChanged();
|
||||
|
||||
if (component == nullptr)
|
||||
return;
|
||||
|
||||
lastPeer = peer;
|
||||
lastPeerID = peerID;
|
||||
}
|
||||
|
||||
unregister();
|
||||
|
|
@ -70439,7 +70440,9 @@ public:
|
|||
}
|
||||
|
||||
resizeToBestWindowPos();
|
||||
addToDesktop (ComponentPeer::windowIsTemporary | getLookAndFeel().getMenuWindowFlags());
|
||||
addToDesktop (ComponentPeer::windowIsTemporary
|
||||
| ComponentPeer::windowIgnoresKeyPresses
|
||||
| getLookAndFeel().getMenuWindowFlags());
|
||||
|
||||
getActiveWindows().add (this);
|
||||
Desktop::getInstance().addGlobalMouseListener (this);
|
||||
|
|
@ -70983,7 +70986,7 @@ private:
|
|||
|
||||
for (int col = 0; col < numColumns; ++col)
|
||||
{
|
||||
int i, colW = 50, colH = 0;
|
||||
int i, colW = standardItemHeight, colH = 0;
|
||||
|
||||
const int numChildren = jmin (items.size() - childNum,
|
||||
(items.size() + numColumns - 1) / numColumns);
|
||||
|
|
@ -78530,6 +78533,7 @@ BEGIN_JUCE_NAMESPACE
|
|||
//#define JUCE_ENABLE_REPAINT_DEBUGGING 1
|
||||
|
||||
static Array <ComponentPeer*> heavyweightPeers;
|
||||
static uint32 lastUniqueID = 1;
|
||||
|
||||
ComponentPeer::ComponentPeer (Component* const component_, const int styleFlags_)
|
||||
: component (component_),
|
||||
|
|
@ -78537,6 +78541,7 @@ ComponentPeer::ComponentPeer (Component* const component_, const int styleFlags_
|
|||
lastPaintTime (0),
|
||||
constrainer (nullptr),
|
||||
lastDragAndDropCompUnderMouse (nullptr),
|
||||
uniqueID (lastUniqueID += 2), // increment by 2 so that this can never hit 0
|
||||
fakeMouseMessageSent (false),
|
||||
isWindowMinimised (false)
|
||||
{
|
||||
|
|
@ -78546,7 +78551,6 @@ ComponentPeer::ComponentPeer (Component* const component_, const int styleFlags_
|
|||
ComponentPeer::~ComponentPeer()
|
||||
{
|
||||
heavyweightPeers.removeValue (this);
|
||||
|
||||
Desktop::getInstance().triggerFocusCallback();
|
||||
}
|
||||
|
||||
|
|
@ -78603,9 +78607,9 @@ void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo)
|
|||
{
|
||||
Graphics g (&contextToPaintTo);
|
||||
|
||||
#if JUCE_ENABLE_REPAINT_DEBUGGING
|
||||
#if JUCE_ENABLE_REPAINT_DEBUGGING
|
||||
g.saveState();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
JUCE_TRY
|
||||
{
|
||||
|
|
@ -78613,7 +78617,7 @@ void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo)
|
|||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
|
||||
#if JUCE_ENABLE_REPAINT_DEBUGGING
|
||||
#if JUCE_ENABLE_REPAINT_DEBUGGING
|
||||
// enabling this code will fill all areas that get repainted with a colour overlay, to show
|
||||
// clearly when things are being repainted.
|
||||
g.restoreState();
|
||||
|
|
@ -78622,7 +78626,7 @@ void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo)
|
|||
(uint8) Random::getSystemRandom().nextInt (255),
|
||||
(uint8) Random::getSystemRandom().nextInt (255),
|
||||
(uint8) 0x50));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/** If this fails, it's probably be because your CPU floating-point precision mode has
|
||||
been set to low.. This setting is sometimes changed by things like Direct3D, and can
|
||||
|
|
@ -274275,6 +274279,9 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons
|
|||
{
|
||||
UITouch* touch = [touches objectAtIndex: i];
|
||||
|
||||
if ([touch phase] == UITouchPhaseStationary)
|
||||
continue;
|
||||
|
||||
CGPoint p = [touch locationInView: view];
|
||||
const Point<int> pos ((int) p.x, (int) p.y);
|
||||
juce_lastMousePos = pos + getScreenPosition();
|
||||
|
|
@ -274292,14 +274299,27 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons
|
|||
currentTouches.set (touchIndex, touch);
|
||||
}
|
||||
|
||||
ModifierKeys modsToSend (currentModifiers);
|
||||
|
||||
if (isDown)
|
||||
{
|
||||
currentModifiers = currentModifiers.withoutMouseButtons();
|
||||
handleMouseEvent (touchIndex, pos, currentModifiers, time);
|
||||
if ([touch phase] != UITouchPhaseBegan)
|
||||
continue;
|
||||
|
||||
currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
|
||||
modsToSend = currentModifiers;
|
||||
|
||||
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
|
||||
handleMouseEvent (touchIndex, pos, modsToSend.withoutMouseButtons(), time);
|
||||
if (! isValidPeer (this)) // (in case this component was deleted by the event)
|
||||
return;
|
||||
}
|
||||
else if (isUp)
|
||||
{
|
||||
if (! ([touch phase] == UITouchPhaseEnded || [touch phase] == UITouchPhaseCancelled))
|
||||
continue;
|
||||
|
||||
modsToSend = modsToSend.withoutMouseButtons();
|
||||
currentTouches.set (touchIndex, nil);
|
||||
|
||||
int totalActiveTouches = 0;
|
||||
|
|
@ -274317,7 +274337,16 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons
|
|||
currentModifiers = currentModifiers.withoutMouseButtons();
|
||||
}
|
||||
|
||||
handleMouseEvent (touchIndex, pos, currentModifiers, time);
|
||||
handleMouseEvent (touchIndex, pos, modsToSend, time);
|
||||
if (! isValidPeer (this)) // (in case this component was deleted by the event)
|
||||
return;
|
||||
|
||||
if (isUp || isCancel)
|
||||
{
|
||||
handleMouseEvent (touchIndex, Point<int> (-1, -1), currentModifiers, time);
|
||||
if (! isValidPeer (this))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -274347,7 +274376,7 @@ void UIViewComponentPeer::viewFocusLoss()
|
|||
|
||||
void juce_HandleProcessFocusChange()
|
||||
{
|
||||
if (UIViewComponentPeer::isValidPeer (currentlyFocusedPeer))
|
||||
if (ComponentPeer::isValidPeer (currentlyFocusedPeer))
|
||||
{
|
||||
if (Process::isForegroundProcess())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ namespace JuceDummyNamespace {}
|
|||
*/
|
||||
#define JUCE_MAJOR_VERSION 1
|
||||
#define JUCE_MINOR_VERSION 53
|
||||
#define JUCE_BUILDNUMBER 84
|
||||
#define JUCE_BUILDNUMBER 85
|
||||
|
||||
/** Current Juce version number.
|
||||
|
||||
|
|
@ -58912,7 +58912,7 @@ public:
|
|||
private:
|
||||
|
||||
WeakReference<Component> component;
|
||||
ComponentPeer* lastPeer;
|
||||
uint32 lastPeerID;
|
||||
Array <Component*> registeredParentComps;
|
||||
bool reentrant, wasShowing;
|
||||
Rectangle<int> lastBounds;
|
||||
|
|
@ -64972,6 +64972,11 @@ public:
|
|||
*/
|
||||
int getStyleFlags() const noexcept { return styleFlags; }
|
||||
|
||||
/** Returns a unique ID for this peer.
|
||||
Each peer that is created is given a different ID.
|
||||
*/
|
||||
uint32 getUniqueID() const noexcept { return uniqueID; }
|
||||
|
||||
/** Returns the raw handle to whatever kind of window is being used.
|
||||
|
||||
On windows, this is probably a HWND, on the mac, it's likely to be a WindowRef,
|
||||
|
|
@ -65231,6 +65236,7 @@ private:
|
|||
|
||||
WeakReference<Component> lastFocusedComponent, dragAndDropTargetComponent;
|
||||
Component* lastDragAndDropCompUnderMouse;
|
||||
const uint32 uniqueID;
|
||||
bool fakeMouseMessageSent : 1, isWindowMinimised : 1;
|
||||
|
||||
friend class Component;
|
||||
|
|
|
|||
|
|
@ -965,8 +965,7 @@ AudioProcessorGraph::Node* AudioProcessorGraph::getNodeForId (const uint32 nodeI
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor,
|
||||
uint32 nodeId)
|
||||
AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const newProcessor, uint32 nodeId)
|
||||
{
|
||||
if (newProcessor == nullptr)
|
||||
{
|
||||
|
|
@ -983,9 +982,10 @@ AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const n
|
|||
// you can't add a node with an id that already exists in the graph..
|
||||
jassert (getNodeForId (nodeId) == nullptr);
|
||||
removeNode (nodeId);
|
||||
}
|
||||
|
||||
lastNodeId = nodeId;
|
||||
if (nodeId > lastNodeId)
|
||||
lastNodeId = nodeId;
|
||||
}
|
||||
|
||||
Node* const n = new Node (nodeId, newProcessor);
|
||||
nodes.add (n);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
*/
|
||||
#define JUCE_MAJOR_VERSION 1
|
||||
#define JUCE_MINOR_VERSION 53
|
||||
#define JUCE_BUILDNUMBER 84
|
||||
#define JUCE_BUILDNUMBER 85
|
||||
|
||||
/** Current Juce version number.
|
||||
|
||||
|
|
|
|||
|
|
@ -593,13 +593,13 @@ void Component::addToDesktop (int styleWanted, void* nativeWindowToAttachTo)
|
|||
{
|
||||
WeakReference<Component> safePointer (this);
|
||||
|
||||
#if JUCE_LINUX
|
||||
#if JUCE_LINUX
|
||||
// it's wise to give the component a non-zero size before
|
||||
// putting it on the desktop, as X windows get confused by this, and
|
||||
// a (1, 1) minimum size is enforced here.
|
||||
setSize (jmax (1, getWidth()),
|
||||
jmax (1, getHeight()));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
const Point<int> topLeft (getScreenPosition());
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,13 @@ BEGIN_JUCE_NAMESPACE
|
|||
|
||||
#include "juce_ComponentMovementWatcher.h"
|
||||
#include "../../../containers/juce_ScopedValueSetter.h"
|
||||
#include "../windows/juce_ComponentPeer.h"
|
||||
|
||||
|
||||
//==============================================================================
|
||||
ComponentMovementWatcher::ComponentMovementWatcher (Component* const component_)
|
||||
: component (component_),
|
||||
lastPeer (nullptr),
|
||||
lastPeerID (0),
|
||||
reentrant (false),
|
||||
wasShowing (component_->isShowing())
|
||||
{
|
||||
|
|
@ -61,15 +62,16 @@ void ComponentMovementWatcher::componentParentHierarchyChanged (Component&)
|
|||
const ScopedValueSetter<bool> setter (reentrant, true);
|
||||
|
||||
ComponentPeer* const peer = component->getPeer();
|
||||
const uint32 peerID = peer != nullptr ? peer->getUniqueID() : 0;
|
||||
|
||||
if (peer != lastPeer)
|
||||
if (peerID != lastPeerID)
|
||||
{
|
||||
componentPeerChanged();
|
||||
|
||||
if (component == nullptr)
|
||||
return;
|
||||
|
||||
lastPeer = peer;
|
||||
lastPeerID = peerID;
|
||||
}
|
||||
|
||||
unregister();
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ public:
|
|||
private:
|
||||
//==============================================================================
|
||||
WeakReference<Component> component;
|
||||
ComponentPeer* lastPeer;
|
||||
uint32 lastPeerID;
|
||||
Array <Component*> registeredParentComps;
|
||||
bool reentrant, wasShowing;
|
||||
Rectangle<int> lastBounds;
|
||||
|
|
|
|||
|
|
@ -297,7 +297,9 @@ public:
|
|||
}
|
||||
|
||||
resizeToBestWindowPos();
|
||||
addToDesktop (ComponentPeer::windowIsTemporary | getLookAndFeel().getMenuWindowFlags());
|
||||
addToDesktop (ComponentPeer::windowIsTemporary
|
||||
| ComponentPeer::windowIgnoresKeyPresses
|
||||
| getLookAndFeel().getMenuWindowFlags());
|
||||
|
||||
getActiveWindows().add (this);
|
||||
Desktop::getInstance().addGlobalMouseListener (this);
|
||||
|
|
@ -849,7 +851,7 @@ private:
|
|||
|
||||
for (int col = 0; col < numColumns; ++col)
|
||||
{
|
||||
int i, colW = 50, colH = 0;
|
||||
int i, colW = standardItemHeight, colH = 0;
|
||||
|
||||
const int numChildren = jmin (items.size() - childNum,
|
||||
(items.size() + numColumns - 1) / numColumns);
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ BEGIN_JUCE_NAMESPACE
|
|||
|
||||
//==============================================================================
|
||||
static Array <ComponentPeer*> heavyweightPeers;
|
||||
|
||||
static uint32 lastUniqueID = 1;
|
||||
|
||||
//==============================================================================
|
||||
ComponentPeer::ComponentPeer (Component* const component_, const int styleFlags_)
|
||||
|
|
@ -51,6 +51,7 @@ ComponentPeer::ComponentPeer (Component* const component_, const int styleFlags_
|
|||
lastPaintTime (0),
|
||||
constrainer (nullptr),
|
||||
lastDragAndDropCompUnderMouse (nullptr),
|
||||
uniqueID (lastUniqueID += 2), // increment by 2 so that this can never hit 0
|
||||
fakeMouseMessageSent (false),
|
||||
isWindowMinimised (false)
|
||||
{
|
||||
|
|
@ -60,7 +61,6 @@ ComponentPeer::ComponentPeer (Component* const component_, const int styleFlags_
|
|||
ComponentPeer::~ComponentPeer()
|
||||
{
|
||||
heavyweightPeers.removeValue (this);
|
||||
|
||||
Desktop::getInstance().triggerFocusCallback();
|
||||
}
|
||||
|
||||
|
|
@ -120,9 +120,9 @@ void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo)
|
|||
{
|
||||
Graphics g (&contextToPaintTo);
|
||||
|
||||
#if JUCE_ENABLE_REPAINT_DEBUGGING
|
||||
#if JUCE_ENABLE_REPAINT_DEBUGGING
|
||||
g.saveState();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
JUCE_TRY
|
||||
{
|
||||
|
|
@ -130,7 +130,7 @@ void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo)
|
|||
}
|
||||
JUCE_CATCH_EXCEPTION
|
||||
|
||||
#if JUCE_ENABLE_REPAINT_DEBUGGING
|
||||
#if JUCE_ENABLE_REPAINT_DEBUGGING
|
||||
// enabling this code will fill all areas that get repainted with a colour overlay, to show
|
||||
// clearly when things are being repainted.
|
||||
g.restoreState();
|
||||
|
|
@ -139,7 +139,7 @@ void ComponentPeer::handlePaint (LowLevelGraphicsContext& contextToPaintTo)
|
|||
(uint8) Random::getSystemRandom().nextInt (255),
|
||||
(uint8) Random::getSystemRandom().nextInt (255),
|
||||
(uint8) 0x50));
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/** If this fails, it's probably be because your CPU floating-point precision mode has
|
||||
been set to low.. This setting is sometimes changed by things like Direct3D, and can
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ public:
|
|||
*/
|
||||
int getStyleFlags() const noexcept { return styleFlags; }
|
||||
|
||||
/** Returns a unique ID for this peer.
|
||||
Each peer that is created is given a different ID.
|
||||
*/
|
||||
uint32 getUniqueID() const noexcept { return uniqueID; }
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the raw handle to whatever kind of window is being used.
|
||||
|
|
@ -374,6 +378,7 @@ private:
|
|||
//==============================================================================
|
||||
WeakReference<Component> lastFocusedComponent, dragAndDropTargetComponent;
|
||||
Component* lastDragAndDropCompUnderMouse;
|
||||
const uint32 uniqueID;
|
||||
bool fakeMouseMessageSent : 1, isWindowMinimised : 1;
|
||||
|
||||
friend class Component;
|
||||
|
|
|
|||
|
|
@ -735,6 +735,9 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons
|
|||
{
|
||||
UITouch* touch = [touches objectAtIndex: i];
|
||||
|
||||
if ([touch phase] == UITouchPhaseStationary)
|
||||
continue;
|
||||
|
||||
CGPoint p = [touch locationInView: view];
|
||||
const Point<int> pos ((int) p.x, (int) p.y);
|
||||
juce_lastMousePos = pos + getScreenPosition();
|
||||
|
|
@ -752,14 +755,27 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons
|
|||
currentTouches.set (touchIndex, touch);
|
||||
}
|
||||
|
||||
ModifierKeys modsToSend (currentModifiers);
|
||||
|
||||
if (isDown)
|
||||
{
|
||||
currentModifiers = currentModifiers.withoutMouseButtons();
|
||||
handleMouseEvent (touchIndex, pos, currentModifiers, time);
|
||||
if ([touch phase] != UITouchPhaseBegan)
|
||||
continue;
|
||||
|
||||
currentModifiers = currentModifiers.withoutMouseButtons().withFlags (ModifierKeys::leftButtonModifier);
|
||||
modsToSend = currentModifiers;
|
||||
|
||||
// this forces a mouse-enter/up event, in case for some reason we didn't get a mouse-up before.
|
||||
handleMouseEvent (touchIndex, pos, modsToSend.withoutMouseButtons(), time);
|
||||
if (! isValidPeer (this)) // (in case this component was deleted by the event)
|
||||
return;
|
||||
}
|
||||
else if (isUp)
|
||||
{
|
||||
if (! ([touch phase] == UITouchPhaseEnded || [touch phase] == UITouchPhaseCancelled))
|
||||
continue;
|
||||
|
||||
modsToSend = modsToSend.withoutMouseButtons();
|
||||
currentTouches.set (touchIndex, nil);
|
||||
|
||||
int totalActiveTouches = 0;
|
||||
|
|
@ -777,7 +793,16 @@ void UIViewComponentPeer::handleTouches (UIEvent* event, const bool isDown, cons
|
|||
currentModifiers = currentModifiers.withoutMouseButtons();
|
||||
}
|
||||
|
||||
handleMouseEvent (touchIndex, pos, currentModifiers, time);
|
||||
handleMouseEvent (touchIndex, pos, modsToSend, time);
|
||||
if (! isValidPeer (this)) // (in case this component was deleted by the event)
|
||||
return;
|
||||
|
||||
if (isUp || isCancel)
|
||||
{
|
||||
handleMouseEvent (touchIndex, Point<int> (-1, -1), currentModifiers, time);
|
||||
if (! isValidPeer (this))
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -808,7 +833,7 @@ void UIViewComponentPeer::viewFocusLoss()
|
|||
|
||||
void juce_HandleProcessFocusChange()
|
||||
{
|
||||
if (UIViewComponentPeer::isValidPeer (currentlyFocusedPeer))
|
||||
if (ComponentPeer::isValidPeer (currentlyFocusedPeer))
|
||||
{
|
||||
if (Process::isForegroundProcess())
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue