I'm doign a file transfer manager on Dialog the needs to dynamically generate a couple of UI elements. Here is the function that generates it:
void TransferData::createGraphicalUI(QDialog *parent, qint32 td_id){
status = new QLabel(parent);
filename = new QLabel(parent);
contact_label = new QLabel(parent);
accept = new IDPushButton(parent,td_id);
reject = new IDPushButton(parent,td_id);
cancel = new IDPushButton(parent,td_id);
progress = new QProgressBar(parent);
statlayout = new QHBoxLayout();
frameLayout = new QVBoxLayout();
frame = new QFrame(parent);
// Stylying the frame
frame->setStyleSheet("background-color: rgb(255, 255, 255);");
// Setting the messages.
QString htmlHeader = "<html><head/><body><p><span style='font-weight:792; color:#0000ff;'>";
QString htmlEnder = "</span></p></body></html>";
QString contactMsg = "Transfer ";
QString filenameMsg = "File name: </span><span>" + getFileToBeSent();
QString statusMsg = "Status: </span><span>";
cancel->setText("Cancel");
cancel->setIcon(QIcon(":/Icons/icons/cancel.png"));
cancel->setVisible(false);
if (getIsATransfer()){
// This is a transfer TO, the file will be uploaded
contactMsg = contactMsg + "to: </span><span> " + getConctacName();
statusMsg = statusMsg + "Waiting for file to be accepted.";
statlayout->addWidget(status);
statlayout->addWidget(cancel);
accept->setVisible(false);
reject->setVisible(false);
}
else{
// This is a transfer FROM, the file will be downlaoded
contactMsg = contactMsg + "from: </span><span> " + getConctacName();
statusMsg = statusMsg + "Transfer must be accepted before it begins.";
accept->setText("Accept");
accept->setIcon(QIcon(":/Icons/icons/ok.png"));
reject->setText("Reject");
reject->setIcon(QIcon(":/Icons/icons/cancel.png"));
statlayout->addWidget(status);
statlayout->addWidget(accept);
statlayout->addWidget(reject);
statlayout->addWidget(cancel);
}
status->setText(htmlHeader + statusMsg + htmlEnder);
filename->setText(htmlHeader + filenameMsg + htmlEnder);
contact_label->setText(htmlHeader + contactMsg + htmlEnder);
// Resettign the progress bar
progress->setValue(0);
// Putting it all together.
frameLayout->addWidget(contact_label);
frameLayout->addWidget(filename);
frameLayout->addLayout(statlayout);
frameLayout->addWidget(progress);
frame->setLayout(frameLayout);
}
This is called from a function that has a list of TransferData objects:
qint32 TransferManager::addTransfer(TransferData td){
// Getting the ID for this tranfer
qint32 transferID = transfers.size();
td.createGraphicalUI(this,transferID);
// Adding it to the UI
ui->globalTMLayout->addWidget(td.getFrame());
connect(td.getAcceptButton(),SIGNAL(wasClicked(qint32)),this,SLOT(onTransferAccepted(qint32)));
connect(td.getRejectButton(),SIGNAL(wasClicked(qint32)),this,SLOT(onTransferRejected(qint32)));
connect(td.getCancelButton(),SIGNAL(wasClicked(qint32)),this,SLOT(onTransferCanceled(qint32)));
// Adding the TD
transfers << td;
// If a transfer is added this needs to be shown
this->show();
return transferID;
}
Once the transfer is done I need to delete all the elements of the created UI. I do it like this:
void TransferManager::removeTransferData(qint32 which){
if (which < transfers.size()){
// Deleting the UI
transfers[which].removeGraphicalUI();
// Removing the frame
QFrame *frame = transfers.at(which).getFrame();
ui->globalTMLayout->removeWidget(frame);
// Removing the data itself
transfers.removeAt(which);
}
}
Where removeGraphicalUI is this function:
void TransferData::removeGraphicalUI(){
frameLayout->removeWidget(progress);
frameLayout->removeWidget(filename);
frameLayout->removeWidget(contact_label);
statlayout->removeWidget(cancel);
statlayout->removeWidget(status);
if (!getIsATransfer()){
statlayout->removeWidget(accept);
statlayout->removeWidget(reject);
}
}
What happens is that the frame is removed but everythign that was inside the frame remains. I've checked with a printed message an the code IS enering the removeUI function.
So why does this not work and what Is the proper way to delete dinamically generated UI?
Thanks!
Ok, so I haven't found the answer to my question but I did find the way to do this:
Basically the documentation says that when a QWidget descendant object is deleted, all their childs are deleted.
So what I did is basically used the QFrame as the parent for everything BUT the the layouts.
Then when I wanted to delete said frame I would simply invoke:
frame->~QFrame()
In the removeGraphicalUI() function and NOTHING more. Also I've commented this:
void TransferManager::removeTransferData(qint32 which){
if (which < transfers.size()){
// Deleting the UI
transfers[which].removeGraphicalUI();
// Removing the frame
// QFrame *frame = transfers.at(which).getFrame();
// ui->globalTMLayout->removeWidget(frame);
// Removing the data itself
transfers.removeAt(which);
}
}
As removing the frame from the GUI was no longer necessary. I hope this helps someone.
Related
I have integrated the NDI SDK from NewTek in the current version 5 into my Qt6.3 widget project.
I copied and included the required DLLs and header files from the NDI SDK installation directory into my project.
To test my build environment I tried to compile a simple test program based on the example from "..\NDI 5 SDK\Examples\C++\NDIlib_Recv".
That was also successful.
I was therefore able to receive or access data from my NDI source.
There is therefore a valid frame in the video_frame of the type NDIlib_video_frame_v2_t. Within the structure I can also query correct data of the frame such as the size (.xres and .yres).
The pointer p_data points to the actual data.
So far so good.
Of course, I now want to display this frame on the Qt6 GUI. In other words, the only thing missing now is the conversion into an appropriate format so that I can display the frame with QImage, QPixmap, QLabel, etc.
But how?
So far I've tried calls like this:
curFrame = QImage(video_frame.p_data, video_frame.xres, video_frame.yres, QImage::Format::Format_RGB888);
curFrame.save("out.jpg");
I'm not sure if the format is correct either.
Here's a closer look at the mentioned frame structure within the Qt debug session:
my NDI video frame in the Qt Debug session, after receiving
Within "video_frame" you can see the specification video_type_UYVY.
This may really be the format as it appears at the source!?
Fine, but how do I get this converted now?
Many thanks and best regards
You mean something like this? :)
https://github.com/NightVsKnight/QtNdiMonitorCapture
Specifically:
https://github.com/NightVsKnight/QtNdiMonitorCapture/blob/main/lib/ndireceiverworker.cpp
Assuming you connect using NDIlib_recv_color_format_best:
NDIlib_recv_create_v3_t recv_desc;
recv_desc.p_ndi_recv_name = "QtNdiMonitorCapture";
recv_desc.source_to_connect_to = ...;
recv_desc.color_format = NDIlib_recv_color_format_best;
recv_desc.bandwidth = NDIlib_recv_bandwidth_highest;
recv_desc.allow_video_fields = true;
pNdiRecv = NDIlib_recv_create_v3(&recv_desc);
Then when you receive a NDIlib_video_frame_v2_t:
void NdiReceiverWorker::processVideo(
NDIlib_video_frame_v2_t *pNdiVideoFrame,
QList<QVideoSink*> *videoSinks)
{
auto ndiWidth = pNdiVideoFrame->xres;
auto ndiHeight = pNdiVideoFrame->yres;
auto ndiLineStrideInBytes = pNdiVideoFrame->line_stride_in_bytes;
auto ndiPixelFormat = pNdiVideoFrame->FourCC;
auto pixelFormat = NdiWrapper::ndiPixelFormatToPixelFormat(ndiPixelFormat);
if (pixelFormat == QVideoFrameFormat::PixelFormat::Format_Invalid)
{
qDebug().nospace() << "Unsupported pNdiVideoFrame->FourCC " << NdiWrapper::ndiFourCCToString(ndiPixelFormat) << "; return;";
return;
}
QSize videoFrameSize(ndiWidth, ndiHeight);
QVideoFrameFormat videoFrameFormat(videoFrameSize, pixelFormat);
QVideoFrame videoFrame(videoFrameFormat);
if (!videoFrame.map(QVideoFrame::WriteOnly))
{
qWarning() << "videoFrame.map(QVideoFrame::WriteOnly) failed; return;";
return;
}
auto pDstY = videoFrame.bits(0);
auto pSrcY = pNdiVideoFrame->p_data;
auto pDstUV = videoFrame.bits(1);
auto pSrcUV = pSrcY + (ndiLineStrideInBytes * ndiHeight);
for (int line = 0; line < ndiHeight; ++line)
{
memcpy(pDstY, pSrcY, ndiLineStrideInBytes);
pDstY += ndiLineStrideInBytes;
pSrcY += ndiLineStrideInBytes;
if (pDstUV)
{
// For now QVideoFrameFormat/QVideoFrame does not support P216. :(
// I have started the conversation to have it added, but that may take awhile. :(
// Until then, copying only every other UV line is a cheap way to downsample P216's 4:2:2 to P016's 4:2:0 chroma sampling.
// There are still a few visible artifacts on the screen, but it is passable.
if (line % 2)
{
memcpy(pDstUV, pSrcUV, ndiLineStrideInBytes);
pDstUV += ndiLineStrideInBytes;
}
pSrcUV += ndiLineStrideInBytes;
}
}
videoFrame.unmap();
foreach(QVideoSink *videoSink, *videoSinks)
{
videoSink->setVideoFrame(videoFrame);
}
}
QVideoFrameFormat::PixelFormat NdiWrapper::ndiPixelFormatToPixelFormat(enum NDIlib_FourCC_video_type_e ndiFourCC)
{
switch(ndiFourCC)
{
case NDIlib_FourCC_video_type_UYVY:
return QVideoFrameFormat::PixelFormat::Format_UYVY;
case NDIlib_FourCC_video_type_UYVA:
return QVideoFrameFormat::PixelFormat::Format_UYVY;
break;
// Result when requesting NDIlib_recv_color_format_best
case NDIlib_FourCC_video_type_P216:
return QVideoFrameFormat::PixelFormat::Format_P016;
//case NDIlib_FourCC_video_type_PA16:
// return QVideoFrameFormat::PixelFormat::?;
case NDIlib_FourCC_video_type_YV12:
return QVideoFrameFormat::PixelFormat::Format_YV12;
//case NDIlib_FourCC_video_type_I420:
// return QVideoFrameFormat::PixelFormat::?
case NDIlib_FourCC_video_type_NV12:
return QVideoFrameFormat::PixelFormat::Format_NV12;
case NDIlib_FourCC_video_type_BGRA:
return QVideoFrameFormat::PixelFormat::Format_BGRA8888;
case NDIlib_FourCC_video_type_BGRX:
return QVideoFrameFormat::PixelFormat::Format_BGRX8888;
case NDIlib_FourCC_video_type_RGBA:
return QVideoFrameFormat::PixelFormat::Format_RGBA8888;
case NDIlib_FourCC_video_type_RGBX:
return QVideoFrameFormat::PixelFormat::Format_RGBX8888;
default:
return QVideoFrameFormat::PixelFormat::Format_Invalid;
}
}
When I have a dialog and multiple buttons have the same click-callback (action method) I mostly want to know which button has been pressed. How do I do that?
Example:
class ButtonDialog : UIFrame{
void button_pressed(object self){
// how do I get this button
TagGroup pressed_button = ?;
result("The button " + pressed_button + " is pressed.\n");
}
object init(object self){
TagGroup dlg, dlg_items, button1, button2;
dlg = DLGCreateDialog("Press a button", dlg_items);
button1 = DLGCreatePushButton("Button 1", "button_pressed");
button1.DLGIdentifier("button1");
dlg_items.DLGAddElement(button1);
button2 = DLGCreatePushButton("Button 2", "button_pressed");
button2.DLGIdentifier("button2");
dlg_items.DLGAddElement(button2);
self.super.init(dlg);
return self;
}
}
object dialog = alloc(ButtonDialog).init();
dialog.pose();
In my current program I have multiple rows created from a TagGroup. Each row has multiple buttons doing the same thing but for their specific row. Therefore I need to know which button it is to get the row to modify. Also the length of the TagGroup and therefore the row count is not fixed. So I cannot create button1_pressed, button2_pressed, ... functions except with doing some weird stuff with code evaluation on the fly which I want to avoid if possible.
The fact that you cannot pass an argument in the simple call-back of a push-button is a bit of a bummer. The easiest solution (albeit not elegant) is to use simple-one-line-callback methods which are unique but themselves call a generalized method as in the code below.
Of course mile7 stated that the number of buttons isn't fixed at compile time, which is an issue here. But unless the (potential) number of buttons is legion, this approach is still the easiest and cleanest, and as each "hard coded" call-back is only one line with a very systemic change, it should be fairly trivial to use Notepad++ or similar to provide an extensive enough set of such calls. (It doesn't hurt if some of them are actually never used.)
class ButtonDialog : UIFrame{
void button_pressed(object self, string buttonID){
// how do I get this button
TagGroup pressed_button = self.LookUpElement(buttonID);
if ( pressed_button.TagGroupIsValid() )
result("The button " + buttonID + " is pressed.\n");
else
result("The button " + buttonID + " was not found!\n");
}
// Need to be done for each button
void button_pressed_0(object self) { self.button_pressed("button0"); }
void button_pressed_1(object self) { self.button_pressed("button1"); }
object init(object self){
TagGroup dlg, dlg_items
dlg = DLGCreateDialog("Press a button", dlg_items);
number nButtons = 2
for( number n=0; n<nButtons; n++ ){
TagGroup button = DLGCreatePushButton("Button " + n , "button_pressed_" + n);
button.DLGIdentifier("button" + n);
dlg_items.DLGAddElement(button);
}
self.super.init(dlg);
return self;
}
}
object dialog = alloc(ButtonDialog).init();
dialog.pose();
So I kind of found an answer which I can live with, but I'm still hoping for better results.
My current idea is to use DualStateBevelButtons. If a button gets clicked, the state changes and the callback is executed. Then all buttons are checked if they have a changed state. If so, this is the clicked button and the state is reset.
The very very big downside of this solution is, that there are only buttons with images allowed, no text is possible. So this is not really the general solution to work with.
rgbimage button_img = RGBImage("button-image", 4, 16, 16);
button_img = max(0, 255 - iradius / 8 * 255);
class ButtonDialog : UIFrame{
TagGroup buttons;
void button_pressed(object self){
for(number i = 0; i < buttons.TagGroupCountTags(); i++){
TagGroup button;
if(buttons.TagGroupGetIndexedTagAsTagGroup(i, button)){
if(button.DLGGetValue() == 1){
// this button is toggled so it is clicked
string identifier;
button.DLGGetIdentifier(identifier);
result("Button " + i + " (" + identifier + ") is clicked.\n");
// reset button state
button.DLGBevelButtonOn(0);
// do not continue searching, found pressed button already
break;
}
}
}
}
object init(object self){
TagGroup dlg, dlg_items, button1, button2;
dlg = DLGCreateDialog("Press a button", dlg_items);
buttons = NewTagList();
button1 = DLGCreateDualStateBevelButton("button1", button_img, button_img, "button_pressed");
buttons.TagGroupInsertTagAsTagGroup(infinity(), button1);
dlg_items.DLGAddElement(button1);
button2 = DLGCreateDualStateBevelButton("button2", button_img, button_img, "button_pressed");
buttons.TagGroupInsertTagAsTagGroup(infinity(), button2);
dlg_items.DLGAddElement(button2);
self.super.init(dlg);
return self;
}
}
object dialog = alloc(ButtonDialog).init();
dialog.pose();
I have a treeTable with editable cells within the expanded rows. The editable cells get a dirty flag after editing (in the example the background color is set to red).
The problem i'm running into is that i found no certain way to update the dirty flag on expand/collapse (edited cells get the css class 'edited-cell').
At the moment the code looks like that:
// each editable textfield gets a Listener
textField.attachLiveChange(
var source = oEvent.oSource;
...
jQuery('#' + source.getId()).addClass(EDITED_CELL_CLASS, false)
// list with cell ids, e.g. "__field1-col1-row1"
DIRTY_MODELS.push(model.getId()) //*** add also binding context of row
)
// the table rows are updated on toggleOpenState
new sap.ui.table.TreeTable({
toggleOpenState: function(oEvent) {
...
this.updateRows() // see function below
}
})
// update Rows function is also delegated
oTable.addDelegate({ onAfterRendering : jQuery.proxy(this.updateRows, oTable)});
//http://stackoverflow.com/questions/23683627/access-row-for-styling-in-sap-ui5-template-handler-using-jquery
// this method is called on each expand/collapse: here i can make sure that the whole row has it's correct styling...
// but how to make sure that special cells are dirty?
function updateRows(oEvent) {
if (oEvent.type !== 'AfterRendering'){
this.onvscroll(oEvent);
}
var rows = this.getVisibleRowCount();
var rowStart = this.getFirstVisibleRow();
var actualRow;
for (var i = 0; i < rows; i++){
actualRow = this.getContextByIndex(rowStart + i); //content
var row = this.getRows()[i]
var obj = actualRow.getObject()
var rowId = row.getId()
updateStyleOfRows(obj, rowId, actualRow)
updateDirtyCells(rowId) //*** how to get the binding context in this function???
}
};
// update Dirty Cells in function updateRows():
function updateDirtyCells(rowId){
for (var i = 0; i < DIRTY_MODELS.length; i++){
var dirtyCellId = DIRTY_MODELS[i]
//*** make sure that only the correct expanded/collapsed rows will be updated -> depends on the bindingContext of the row
jQuery('#' + rowId).find('#' + dirtyCellId + '.editable-cell').addClass(EDITED_CELL_CLASS, false)
}
}
This doesn't work correctly, because the ids of the cells change on each layout render (e.g. collapse/expand rows). Please see attached image.
Let me know if i should provide more information.
I was setting up a storm cluster to calculate real time trending and other statistics, however I have some problems introducing the "recovery" feature into this project, by allowing the offset that was last read by the kafka-spout (the source code for kafka-spout comes from https://github.com/apache/incubator-storm/tree/master/external/storm-kafka) to be remembered. I start my kafka-spout in this way:
BrokerHosts zkHost = new ZkHosts("localhost:2181");
SpoutConfig kafkaConfig = new SpoutConfig(zkHost, "test", "", "test");
kafkaConfig.forceFromStart = false;
KafkaSpout kafkaSpout = new KafkaSpout(kafkaConfig);
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("test" + "spout", kafkaSpout, ESConfig.spoutParallelism);
The default settings should be doing this, but I think it is not doing so in my case, every time I start my project, the PartitionManager tries to look for the file with the offsets, then nothing is found:
2014-06-25 11:57:08 INFO PartitionManager:73 - Read partition information from: /storm/partition_1 --> null
2014-06-25 11:57:08 INFO PartitionManager:86 - No partition information found, using configuration to determine offset
Then it starts reading from the latest possible offset. Which is okay if my project never fails, but not exactly what I wanted.
I also looked a bit more into the PartitionManager class which uses Zkstate class to write the offsets, from this code snippet:
PartitionManeger
public void commit() {
long lastCompletedOffset = lastCompletedOffset();
if (_committedTo != lastCompletedOffset) {
LOG.debug("Writing last completed offset (" + lastCompletedOffset + ") to ZK for " + _partition + " for topology: " + _topologyInstanceId);
Map<Object, Object> data = (Map<Object, Object>) ImmutableMap.builder()
.put("topology", ImmutableMap.of("id", _topologyInstanceId,
"name", _stormConf.get(Config.TOPOLOGY_NAME)))
.put("offset", lastCompletedOffset)
.put("partition", _partition.partition)
.put("broker", ImmutableMap.of("host", _partition.host.host,
"port", _partition.host.port))
.put("topic", _spoutConfig.topic).build();
_state.writeJSON(committedPath(), data);
_committedTo = lastCompletedOffset;
LOG.debug("Wrote last completed offset (" + lastCompletedOffset + ") to ZK for " + _partition + " for topology: " + _topologyInstanceId);
} else {
LOG.debug("No new offset for " + _partition + " for topology: " + _topologyInstanceId);
}
}
ZkState
public void writeBytes(String path, byte[] bytes) {
try {
if (_curator.checkExists().forPath(path) == null) {
_curator.create()
.creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.forPath(path, bytes);
} else {
_curator.setData().forPath(path, bytes);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
I could see that for the first message, the writeBytes method gets into the if block and tries to create a path, then for the second message it goes into the else block, which seems to be ok. But when I start the project again, the same message as mentioned above shows up. No partition information can be found.
I had the same problem. Turned out I was running in local mode which uses an in memory zookeeper and not the zookeeper that Kafka is using.
To make sure that KafkaSpout doesn't use Storm's ZooKeeper for the ZkState that stores the offset, you need to set the SpoutConfig.zkServers, SpoutConfig.zkPort, and SpoutConfig.zkRoot in addition to the ZkHosts. For example
import org.apache.zookeeper.client.ConnectStringParser;
import storm.kafka.SpoutConfig;
import storm.kafka.ZkHosts;
import storm.kafka.KeyValueSchemeAsMultiScheme;
...
final ConnectStringParser connectStringParser = new ConnectStringParser(zkConnectStr);
final List<InetSocketAddress> serverInetAddresses = connectStringParser.getServerAddresses();
final List<String> serverAddresses = new ArrayList<>(serverInetAddresses.size());
final Integer zkPort = serverInetAddresses.get(0).getPort();
for (InetSocketAddress serverInetAddress : serverInetAddresses) {
serverAddresses.add(serverInetAddress.getHostName());
}
final ZkHosts zkHosts = new ZkHosts(zkConnectStr);
zkHosts.brokerZkPath = kafkaZnode + zkHosts.brokerZkPath;
final SpoutConfig spoutConfig = new SpoutConfig(zkHosts, inputTopic, kafkaZnode, kafkaConsumerGroup);
spoutConfig.scheme = new KeyValueSchemeAsMultiScheme(inputKafkaKeyValueScheme);
spoutConfig.zkServers = serverAddresses;
spoutConfig.zkPort = zkPort;
spoutConfig.zkRoot = kafkaZnode;
I think you are hitting this bug:
https://community.hortonworks.com/questions/66524/closedchannelexception-kafka-spout-cannot-read-kaf.html
And the comment from the colleague above fixed my issue. I added some newer libraries to.
I'd like to insert all Labels from a labelModuleId in an AX2009 table.
I have this job, that does nearly everything I need. But I have to enter the max Id (toLabel = 1000):
static void OcShowAllLabel(Args _args)
{
xInfo xinfo;
LanguageId currentLanguageId;
LabelModuleId labelModuleId = 'OCM'; // hier evt eine Eingabe durch Benutzer zur Auswahl
LabelIdNum frLabel;
LabelIdNum toLabel = 1000;
LabelId labelId;
OcShowAllLabels_RS tab;
Label blub = new Label();
str label;
;
xInfo = new xInfo();
currentLanguageId = xInfo.language();
delete_from tab
where tab.LanguageId == currentLanguageId
&& tab.LabelModuleId == labelModuleId;
for (frLabel = 1; frLabel <= toLabel; frLabel++)
{
labelId = strfmt('#%1%2', labelModuleId, frLabel);
label = SysLabel::labelId2String(labelId, currentLanguageId);
if (labelId != label)
{
tab.initValue();
tab.LabelId = labelId;
tab.Label = label;
tab.LanguageId = currentLanguageId;
tab.LabelModuleId = labelModuleId;
tab.insert();
}
}
Info('done');
}
If this is a one-time job, you can just stop the AOS and open the label file in notepad. It's in your application folder called axXXXen-us.ald, where XXX is your label file name and en-us is your language.
Look at classes\Tutorial_ThreadWork\doTheWork to see where they use a while(sLabel) instead of a for loop like you have.
container doTheWork(Thread t,LabelType searchFor)
{
container retVal;
SysLabel sysLabel = new SysLabel(LanguageTable::defaultLanguage());
str slabel;
;
slabel = sysLabel.searchFirst(searchFor);
while (slabel)
{
retVal += sLabel;
slabel = sysLabel.searchNext();
}
return retVal;
}
Since the label file is a text file, it would make sense that you can't just select the last one, but you have to iterate through the file. AX caches the labels however, but I don't believe you can just readily access the label cache as far as I know.
Lastly, hopefully you won't try this, but don't try to just read in the label text file, because AX sometimes has labels that it hasn't flushed to that file from the cache. I think Label::Flush(...) will flush them, but I'm not sure.
Here is another option I suppose. You can insert a label to get the next label number and then just immediately delete it:
static void Job32(Args _args)
{
SysLabel sysLabel = new SysLabel(LanguageTable::defaultLanguage());
SysLabelEdit sysLabelEdit = new SysLabeLEdit();
LabelId labelid;
;
labelId = syslabel.insert('alextest', '', 'OCM');
info(strfmt("%1", labelId));
sysLabelEdit.labelDelete(labelId, false);
}
It does seem to consume the number from the number sequence though. You could just do a Label::Flush(...) and then check the text file via code. Look at Classes\SysLabel* to see some of how the system deals with labels. It doesn't look very simple by any means.
Here is another option that might work for you. This will identify missing labels too. Change 'en-us' to your language. This is a "dirty" alternative I suppose. You might need to add something to say "if we find 5 labels in a row where they're like '#OCM'".
for (i=1; i<999; i++)
{
labelId = strfmt("#%1%2", 'OCM', i);
s = SysLabel::labelId2String(labelId, 'en-us');
if (s like '#OCM*')
{
info (strfmt("%1: Last is %2", i, s));
break;
}
info(strfmt("%1: %2", i, s));
}