GTK TextView auto scroll when text is added to text buffer - gtk

I am trying to create a very simple log-like GUI application that merely displays text from a log file dynamically and asynchronously. The problem is that when the log file is updated, the text view in the GUI scrolls back up to line 1. Every attempt to fix this has failed and I am wondering if I have stumbled across a bug in GTK. Here is a summary of my code:
using Cairo;
using Gtk;
namespace ServerManager {
public class ServerManager : Window {
public TextView text_view;
public TextIter myIter;
public TextMark myMark;
public async void read_something_async (File file) {
var text = new StringBuilder ();
var dis = new DataInputStream (file.read ());
string line;
while ((line = yield dis.read_line_async (Priority.DEFAULT)) != null) {
text.append (line);
text.append_c('\n');
}
this.text_view.buffer.text = text.str;
text_view.buffer.get_end_iter(out myIter);
text_view.scroll_to_iter(myIter, 0, false, 0, 0);
}
public static int main (string[] args) {
Gtk.init (ref args);
var window = new ServerManager ();
// The read-only TextView
window.text_view = new TextView ();
window.text_view.editable = false;
window.text_view.cursor_visible = false;
window.text_view.wrap_mode = Gtk.WrapMode.WORD;
// Add scrolling functionality to the TextView
var scroll = new ScrolledWindow (null, null);
scroll.set_policy (PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
scroll.add (window.text_view);
// Vbox so that our TextView has someplace to live
var vbox = new Box (Orientation.VERTICAL, 0);
vbox.pack_start (scroll, true, true, 0);
window.add (vbox);
window.set_border_width (12);
window.set_position (Gtk.WindowPosition.CENTER);
window.set_default_size (800, 600);
window.destroy.connect (Gtk.main_quit);
window.show_all ();
File file = File.new_for_path ("/home/user/temp.log");
FileMonitor monitor = file.monitor (FileMonitorFlags.NONE, null);
stdout.printf ("Monitoring: %s\n", file.get_path ());
monitor.changed.connect (() => {
window.read_something_async(file);
});
Gtk.main ();
return 0;
}
}
}
I also tried using TextMarks instead of Iters but that had no affect.

Scroll to 1st row happens because read_something_async() deletes the current contents of the buffer and then writes the new one (this is what setting the text property does). Maybe this is what you want but unless you keep track of the scroll location you will lose it.
The reason your scroll_to_iter() didn't work as expected is probably this:
Note that this function uses the currently-computed height of the lines in the text buffer. Line heights are computed in an idle handler; so this function may not have the desired effect if it’s called before the height computations. To avoid oddness, consider using gtk_text_view_scroll_to_mark() which saves a point to be scrolled to after line validation.
Calling TextView.ScrollToMark() with a "right gravity" TextMark should work for you.

Related

Why is draw_picture called so many times?

When I run the following programme:
//compile: valac --pkg gtk+-3.0 sample.vala
using Gtk;
public class Main : Object
{
private Window window;
private Gdk.Pixbuf pixbuf;
private DrawingArea da1;
public Main()
{
window = new Window();
window.destroy.connect (main_quit);
pixbuf = new Gdk.Pixbuf.from_file("sample.jpg");
var box = new Box (Orientation.VERTICAL, 5);
da1 = new DrawingArea();
da1.set_hexpand(true);
da1.set_vexpand(true);
da1.draw.connect((context) => draw_picture(context, pixbuf));
box.pack_start (da1, true, true, 0);
window.add (box);
window.show_all();
}
bool draw_picture(Cairo.Context cr, Gdk.Pixbuf pixbuf)
{
print("draw_picture\n");
int width = da1.get_allocated_width();
int height = da1.get_allocated_height();
var temp = pixbuf.scale_simple(width, height, Gdk.InterpType.BILINEAR);
Gdk.cairo_set_source_pixbuf (cr, temp, 0, 0);
cr.paint ();
return false;
}
static int main(string[] args)
{
Gtk.init(ref args);
var app = new Main();
Gtk.main();
return 0;
}
}
after start I can see 'draw_picture' printed 2 times. When I switch window to terminal, it's displayed additional 7 times. Can anyone explain why and recommend some good book explaining the details?
In order to answer that question for sure, you would need to look at how your window manager works. Probably the redraws that occur when you switch to another window are due to the widget gaining the :backdrop pseudoclass when its window ceases to be the active window.
In general a widget's draw signal is invoked whenever the window manager needs to redraw a portion of the window that includes the widget, and also whenever the widget's active style changes.
If your widget is expensive to redraw then you can avoid drawing the whole thing when it's not necessary. The Cairo context's clip region will be set to the portion which needs redrawing.

How can insert a jlabel inside a jdesktop pane in netbeans. When i tried to drag and drop it didn't work? The desktop pane got displaced

I also tried to enclose the label inside a dekstop pane. But a message flashed that cannot enclose components in a non-empty container. Any help shall be appreciated
Using Netbeans 11.0 I was able to drag a JLabel into a JDesktopPane and I did not see the error you reported. However I believe that JDesktopPane was designed just to be a container for JInternalFrames rather than having other components embedded directly. This was for MDI applications that aren't seen so much these days.
Have a look at the relevant Swing Tutorial for background info.
I have also put a minimal example here which you can paste into an empty class in Netbeans
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JDesktopPane desktop_pane = new JDesktopPane();
frame.getContentPane().add(desktop_pane);
for (int i = 1; i <= 5; ++i) {
JInternalFrame internal = new JInternalFrame(String.format("Window %d", i), true, true, true, true);
internal.setSize(150, 80);
internal.setLocation(i * 50, i * 50);
internal.setVisible(true);
desktop_pane.add(internal);
}
desktop_pane.setPreferredSize(new Dimension(800, 600));
frame.pack();
SwingUtilities.invokeLater(() -> {
frame.setVisible(true);
});
}
It will create a desktop with five resizeable windows in it.

iText Background Opacity

I want to overlay a text with semi-transparent background over an existing text using iText 7. Setting the background opacity for a text element doesn't seem to work (line 1), I can only set it for the whole paragraph (line 2):
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.Paragraph;
import com.itextpdf.layout.element.Text;
import com.itextpdf.layout.property.TextAlignment;
import com.itextpdf.layout.property.VerticalAlignment;
import java.io.IOException;
public class TextBackgroundOpacityTest {
public static void main(String[] args) throws IOException {
try (Document doc = new Document( new PdfDocument(new PdfWriter("TextBackgroundOpacityTest.pdf")))) {
doc.add(new Paragraph(new String(new char[130]).replace("\0", "A")));
// opacity doesn't work for text element
doc.showTextAligned(new Paragraph(new Text("missing background transparency").setBackgroundColor(ColorConstants.WHITE, .8f)), 500, 805, 0, TextAlignment.RIGHT, VerticalAlignment.TOP, 0);
// opacity for the whole paragraph works, but this is not what I want
doc.showTextAligned(new Paragraph("whole pharagraph background transparancy").setBackgroundColor(ColorConstants.WHITE, .8f), 500, 785, 0, TextAlignment.RIGHT, VerticalAlignment.TOP, 0);
}
}
}
How can I overlay a text with a semi-transparent background as show in line 2, but just for the overlayed text, not the whole paragraph? Desired output:
To work around the solution you can use custom renderers. If you look at the BlockRenderer#drawBackground which is called in case you set transparent background to a paragraph you can see the following lines there:
TransparentColor backgroundColor = new TransparentColor(background.getColor(), background.getOpacity());
drawContext.getCanvas().saveState().setFillColor(backgroundColor.getColor());
backgroundColor.applyFillTransparency(drawContext.getCanvas());
TextRenderer, however, has its own implementation and does not respect transparent background. But we can customize the renderer implementation. We'll need to copy-paste quite a bit of code from the current TextRenderer implementation, but the good news is that we don't need to change a lot of code. Just insert two lines in the right place:
TransparentColor backgroundColor = new TransparentColor(background.getColor(), background.getOpacity());
backgroundColor.applyFillTransparency(drawContext.getCanvas());
Overall we get the following implementation:
private static class TextRendererWithBackgroundOpacity extends TextRenderer {
public TextRendererWithBackgroundOpacity(Text textElement) {
super(textElement);
}
#Override
public void drawBackground(DrawContext drawContext) {
Background background = this.<Background>getProperty(Property.BACKGROUND);
Float textRise = this.getPropertyAsFloat(Property.TEXT_RISE);
Rectangle bBox = getOccupiedAreaBBox();
Rectangle backgroundArea = applyMargins(bBox, false);
float bottomBBoxY = backgroundArea.getY();
float leftBBoxX = backgroundArea.getX();
if (background != null) {
boolean isTagged = drawContext.isTaggingEnabled();
PdfCanvas canvas = drawContext.getCanvas();
if (isTagged) {
canvas.openTag(new CanvasArtifact());
}
boolean backgroundAreaIsClipped = clipBackgroundArea(drawContext, backgroundArea);
canvas.saveState().setFillColor(background.getColor());
TransparentColor backgroundColor = new TransparentColor(background.getColor(), background.getOpacity());
backgroundColor.applyFillTransparency(drawContext.getCanvas());
canvas.rectangle(leftBBoxX - background.getExtraLeft(), bottomBBoxY + (float) textRise - background.getExtraBottom(),
backgroundArea.getWidth() + background.getExtraLeft() + background.getExtraRight(),
backgroundArea.getHeight() - (float) textRise + background.getExtraTop() + background.getExtraBottom());
canvas.fill().restoreState();
if (backgroundAreaIsClipped) {
drawContext.getCanvas().restoreState();
}
if (isTagged) {
canvas.closeTag();
}
}
}
#Override
public IRenderer getNextRenderer() {
return new TextRendererWithBackgroundOpacity((Text)modelElement);
}
}
To make Text element use the custom renderer implementation just call setNextRenderer method:
Text customTextElement = new Text("missing background transparency");
customTextElement.setNextRenderer(new TextRendererWithBackgroundOpacity(customTextElement));
By the way you are very welcome to file the fix as a pull request to iText (please follow the contribution guidelines though). The repository is located at https://github.com/itext/itext7

Total page number with itextpdf

I am using itextpdf-5.5.6 for publish my data.
I'm trying to set page numbers in PdfPCell of my pdf using following format : page_num/total_page_num
For this I use PdfTemplate object filling inside total page number before close document.
It warks, but PdfTemplate exceeds PdfPCell border.
Is it possible that all content of cell stay inside of cell properly ?
PdfTemplate pageNumTemplate;
#Test
public void quick_test() throws FileNotFoundException, DocumentException {
String filename = "C:\\test.pdf";
File file = new File(filename);
Document document = new Document();
PdfWriter writer = PdfWriter.getInstance(document, new FileOutputStream(filename));
PageNumberEvent pageNumberEvent = new PageNumberEvent();
writer.setPageEvent(pageNumberEvent);
document.open();
for(int i = 0; i < 10000; i++){
document.add(new Paragraph("This is my paragraph"));
document.newPage();
}
document.close();
}
public class PageNumberEvent extends PdfPageEventHelper {
#Override
public void onEndPage(PdfWriter writer, Document document) {
if(pageNumTemplate == null){
pageNumTemplate = writer.getDirectContent().createTemplate(0.1f, 0.1f);
}
PdfPTable table = new PdfPTable(1);
table.setSpacingBefore(50);
Paragraph para = new Paragraph();
Chunk pageNum = new Chunk(writer.getPageNumber() + "/");
para.add(pageNum);
Image totalPageNumImg = null;
try {
totalPageNumImg = Image.getInstance(pageNumTemplate);
} catch (BadElementException e) {
e.printStackTrace();
}
Chunk totalPageNumImgChunk = new Chunk(totalPageNumImg, 0, -1, true);
para.add(totalPageNumImgChunk);
para.setIndentationLeft(370);
PdfPCell cell = new PdfPCell(para);
cell.addElement(para);
table.addCell(cell);
try {
document.add(table);
} catch (DocumentException e) {
e.printStackTrace();
}
}
#Override
public void onCloseDocument(PdfWriter writer,Document document) {
String totalPageNumString = String.valueOf(writer.getPageNumber() - 1);
float widthPoint = totalPageNumString.length() * 10;
float heightPoint = totalPageNumString.length() * 20;
Phrase totalPageNumPhrase = new Phrase(totalPageNumString);
Rectangle templRect = pageNumTemplate.getBoundingBox();
Rectangle rectangle = new Rectangle(templRect.getLeft(), templRect.getBottom(), widthPoint, heightPoint + 2);
pageNumTemplate.setBoundingBox(rectangle);
ColumnText.showTextAligned(pageNumTemplate, Element.ALIGN_LEFT, totalPageNumPhrase, 0, 1, 0);
}
}
Early, in the first onEndPage call, you create a minute template (0.1x0.1) to start with
pageNumTemplate = writer.getDirectContent().createTemplate(0.1f, 0.1f);
which fits into your cell. At the end though, in onCloseDocument, after everything has been layout'ed to a fixed position, you resize this template which makes it grow to the left:
Rectangle templRect = pageNumTemplate.getBoundingBox();
Rectangle rectangle = new Rectangle(templRect.getLeft(), templRect.getBottom(), widthPoint, heightPoint + 2);
pageNumTemplate.setBoundingBox(rectangle);
If you want your template to remain in your table cell, you have to initialize it with a width which most likely will be enough to hold the number of pages, e.g.
pageNumTemplate = writer.getDirectContent().createTemplate(30f, 0.1f);
Here you may have to play around a bit...
As the OP indicated in a comment, not only keeping all content of cell stay inside of cell is required but the content also is expected to be right aligned in the cell.
This second requirement obviously cannot be fulfilled by the OP's code which draws the number of pages into the template using ALIGN_LEFT. Furthermore it does not mix and match with resizing the template on the right size because it has already been positioned on the pages.
To fulfill it, therefore, the OP should
horizontally initialize the template with a size large enough for any expected number of pages (onEndPage);
fill the table cell with right alignment (instead of some pseudo right alignment by means of setIndentationLeft) (onEndPage);
leave the horizontal size of the template as is (onCloseDocument); and
show the number of pages right aligned to the right template border (onCloseDocument).

Howto sub class a Clutter.Actor (involves Cairo/Clutter.Canvas)

Can anyone help me get this to run? I'm aiming for a custom Actor. (I have only just started hacking with Vala in the last few days and Clutter is a mystery too.)
The drawme method is being run (when invalidate is called) but there doesn't seem to be any drawing happening (via the Cairo context).
ETA: I added one line in the constructor to show the fix - this.set_size.
/*
Working from the sample code at:
https://developer.gnome.org/clutter/stable/ClutterCanvas.html
*/
public class AnActor : Clutter.Actor {
public Clutter.Canvas canvas;
public AnActor() {
canvas = new Clutter.Canvas();
canvas.set_size(300,300);
this.set_content( canvas );
this.set_size(300,300);
//Connect to the draw signal.
canvas.draw.connect(drawme);
}
private bool drawme( Cairo.Context ctx, int w, int h) {
stdout.printf("Just to test this ran at all: %d\n", w);
ctx.scale(w,h);
ctx.set_source_rgb(0,0,0);
//Rect doesn't draw.
//ctx.rectangle(0,0,200,200);
//ctx.fill();
//paint doesn't draw.
ctx.paint();
return true;
}
}
int main(string [] args) {
// Start clutter.
var result = Clutter.init(ref args);
if (result != Clutter.InitError.SUCCESS) {
stderr.printf("Error: %s\n", result.to_string());
return 1;
}
var stage = Clutter.Stage.get_default();
stage.destroy.connect(Clutter.main_quit);
//Make my custom Actor:
var a = new AnActor();
//This is dodgy:
stage.add_child(a);
//This works:
var r1 = new Clutter.Rectangle();
r1.width = 50;
r1.height = 50;
r1.color = Clutter.Color.from_string("rgb(255, 0, 0)");
stage.add_child(r1);
a.canvas.invalidate();
stage.show_all();
Clutter.main();
return 0;
}
you need to assign a size to the Actor as well, not just the Canvas.
the size of the Canvas is independent of the size of the Actor to which the Canvas is assigned to, as you can assign the same Canvas instance to multiple actors.
if you call:
a.set_size(300, 300)
you will see the actor and the results of the drawing.
Clutter also ships with various examples, for instance how to make a rectangle with rounded corners using Cairo: https://git.gnome.org/browse/clutter/tree/examples/rounded-rectangle.c - or how to make a simple clock: https://git.gnome.org/browse/clutter/tree/examples/canvas.c