LINUX.ORG.RU

Свой GTK виджет, почему странно рисует?


0

1

Виджет унаследован от GtkEntry, рисую на нём «кнопку», но она как-то странно отображается, вернее не сама она, а область рядо с ней, она искажается http://www.imagepost.ru/?v=beg.png хотя в методе expose ьщуго виджета всего лишь один вызов функции:

    static gint
     
    gtk_button_entry_expose(GtkWidget * widget, GdkEventExpose *event)
     
    {
     
           
     
            GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);
     
            gtk_paint_box(  widget->style,
     
                                            event->window,
     
                                            GTK_WIDGET_STATE(widget),
     
                                            GTK_SHADOW_OUT,
     
                                            NULL,
     
                                            NULL,
     
                                            NULL,
     
                                            2,1,14,14);
     
                                           
     
     
     
           
     
            return FALSE;
     
    }

В чём может быть проблема? Куда покопать? :)

★★★★★

Последнее исправление: xterro (всего исправлений: 1)

Ответ на: комментарий от anonymous

Да, будет обрабатываться событие движения курсора, если он над кнопкой(координаты кнопки известны), ставим флаг, и если кликнули мышкой, то генерируем событие и перерисовываем кнопку... Но меня сейчас больше интересует, почеу оно так рисует, такое ощущение, будто под кнопкой рисуется то ли тень, то ли она сама рисуется два раза... не понимаю :(

xterro ★★★★★
() автор топика

Кинь полный исходник - тогда скажу в чем точно проблема. Пока есть лишь догадки. Например, у GtkEntry есть курсор, который «мигает», а ты эту область пытаешься перерисовать.

Я как-то тоже бился с тем, чтобы в GtkEntry дополнительную информацию рисовать. Помню, что было много проблем. Рисовать поверх виджетов - всегда плохая идея, это нарушает инкапсуляцию виджета-предка.

В данном случае - чем тебя не устраивает gtk_entry_set_icon_* ?

snizovtsev ★★★★★
()
Ответ на: комментарий от snizovtsev

Залил сорс сюда http://www.fayloobmennik.net/614796 По поводу курсора, я сдвигаю его(set_inner_border), правда с ним есть ещё одна «особенность», если его установить скажем в 30 пикселей от левого края и начать забивать в GtkEntry текст, то при достижения курсором конца поля(правого края), весь текст начнёт сдвигать влево, не смотря на этот inner_border, т.е если кнопку нарисовать вначале поля, то текст залезет по неё, в архиве есть собранный бинарник, можно увидеть этот эффект наглядно. Что касается иконок, то их можно разместить только две, слева и справа, хочется более универсальное рещение.

xterro ★★★★★
() автор топика

Попробовал просто нарисовать обычную линию, с помощью cairo, на расстоянии 1 пиксель от верхнего края, такая же фигня. Причём, ощущение будто линия «отзеркаливает» на верхнюю сторону виджета. Если нарисовать её на расстоянии 2-3 пикселя от верха, то всё нормально. Причём если рисовать ту-же самую линию снизу, или скажем возле правого края, то всё нормально, запарился уже :(

cairo_set_source_rgb(cr, 0.0,0.0,0.0);
	cairo_set_line_width(cr, 1);
	cairo_move_to(cr, 1,2.5);
	cairo_line_to(cr, 18,2.5);
	cairo_stroke(cr);
	cairo_destroy(cr);

Как-же правильно рисовать на виджете?

xterro ★★★★★
() автор топика
Ответ на: комментарий от xterro

Вкратце, я бы убрал рамку у Entry (через стандартный метод), положил бы его внутрь себя и сам рисовал бы рамку со всякими др. своими элементами, а для Entry бы давал только то место, которое пустое. Рамка рисуется через стиль (см. gtk_paint_box например), который можно получить у самого этого Entry в момент экспоза, т.е. когда вы оба 100% смаплены.

Стандартные виджеты частенько устроены настолько по-идиотски, что лучше *всегда* вместо наследования использовать композицию — наследоваться по вкусу от Container/Bin/[HV]Box и содержать что-то в себе. Да, неудобно что нет методов и ты «уже не тот». Придется писать прокси, или давать публичный доступ через ->entry или ro-проперть (всякие gtk_bin_get_child и без того будут работать).

Если все это делается вообще только для себя, то можно не заниматься писаниной класса, а использовать EventBox с невидимым окном, перехватить size-allocate и expose-event и всего делов.

anonymous
()
Ответ на: комментарий от anonymous

Спасибо, я уже разобрался в чём была причина. Делать составной виджет - он будет выглядеть не родным, «кривым», я изначально пробовал сделать такой, составной виджет. Поэтому решил рисовать на GtkEntry, чтобы всё выглядело красиво и нэйтивно. А причина была в том, что в GtkEntry вложено ещё одно GdkWindow для отрисовки текста, и вот на нём и надо было рисовать кнопку, а не на основном. Сейчас виджет уже рисуется нормально, но есть правда одна проблема, как оказалось у GtkWidget нет события типа resize-event, тут я писал об этом: http://www.linux.org.ru/forum/development/6285965?lastmod=1305888282459#comment-6286732 (комментарий) Т.к мне нужно пересчитывать координаты кнопок на элементе(при изменении размера виджета), когда это делать, если нет события ресайза? Только в событии expose-event, т.е это событие пересчитывает положение конопок и отрисовывает их, имхо неправильное решение, но куда деваться, заупскать из expose-event свой resize-event думаю бедет ещё кривее :)

xterro ★★★★★
() автор топика
Ответ на: комментарий от xterro

Некрасиво было, потому что надо энтрёвым стилем рисовать, см. gtk_rc_get_style() со товарищи. Плюс иногда в gtk_paint_*() надо сувать не свой виджет, а тот, который симулируешь, т.к. некоторые долбанутые движки вроде clearlooks'а бывает на класс виджета смотрят, а он не тот. Мне приходилось даже синглтон создавать, щас уж не помню чего именно, чтобы «быть похожим» на стандартный контрол. Плюс надо подсмотреть что там родной энтри передает как detail. В общем, если других правоверных путей не найдется, то это точно можно.

Насчет size-allocate в соседней ветке — какая-то мистика, т.к. это неотъемлемая часть цикла request/allocate при ресайзе несущего окна. Сорец смотрел, вроде все ок. Можно попробовать через внешний g_signal_connect[_after], если и так не попадет — это чо-то новенькое :\ Также можно посмотреть на искуственную реакцию на gtk_widget_size_allocate(), плюс перенести принтфы перед inherited и на всякий делать fprintf(stderr, "!\n") вместо g_printf(). Ну и раз в экспос заходит, можно сравнить там class->size_request и my_size_request.

Но вышеописанное в принципе не очень нужно, если многострадальная кнопка не имеет своего GdkWindow, т.к. это нормально вычислять координаты для рисования в экспозе, все родные контролы так и делают. Фрейм, например, нигде не хранит прямоугольник с заголовоком, но всегда вычисляет его от общего allocation'а. Ненормально в экспозе — двигать иксовые окна, т.к. будет заметное мельтешение и артефакты могут остаться при ресайзе; для того и есть size-allocate.

arturpub ★★
()
Ответ на: комментарий от xterro

сделай gtkvbox, положи в него еntry (можно без бордюра), поставь боксу margin по левому краю, сделай его app paintable и загаживай как хошь. только с отрисовкой детей надо немного поколдовать

или возьми vala, унаследуйся от gtk.bin и рисуй на здоровье без майндфаков

ckotinko ☆☆☆
()
Ответ на: комментарий от arturpub

Не, некрасиво было не поэтому. Первоначально я делал как: брал HBox, на него клал Entry и Button, убирал у Entry бордюр, и всё это дело упаковывал в Frame, если без фрейма, то смотрелось в принципе не плохо(можно сказать нэйтивно), но это в случае если бы этот «компот» использовался скажем как cell_renderer, но у фрейма, если ему ставить стиль скажем GTK_SHADOW_IN(OUT), вверху появляется «область» из-за которой кнопка просто сжимается(она остаётся такойже квадратной, но маленькой, из-за какого-то бордюра вокруг неё), и кнопка выглядит по уродски(сейчас у меня под рукой нет того кода, или уже собранного бинарника, так бы привёл скриншот или листинг, попробуйте, может получится воспроизвести). Сейчас я рисую сразу на Entry, при отрисовке ставлю detail в «button»:

gtk_paint_box(	widget->style, 
						window, 
					GTK_WIDGET_STATE(widget), 
						shadow_type, 
						&event->area, 
						widget, 
						"button", 
						btn->x, 
						btn->y, 
						btn->w, 
						btn->h);
поэтому выглядит вполне не плохо и нэйтивно, со всеми типами кнопок(в будуще надеюсь добавить тип - картинка, чтобы можно было рисовать на кнопке какой-нибудь pixbuf): http://www.imagepost.ru/?v=full_2.jpg В простейшем случае будет выглядет примерно так: http://www.imagepost.ru/?v=simple.jpg События кликанья по кнопкам уже работают, осталось исправить одну ошибочку(неправильно определяется индекс нажатой кнопки) и допилить установку gtk_entry_set_inner_border, т.к она по идее тоже вызывается в событии expose, то это приводит к «мерцанию» кнопок, ну и имхо тормозам. Что касается gtk_widget_size_allocate() - то эта функция если не ошибаюсь имеет смысл только когда виджет уже отрисован, потому как до этого, она вернёт -1(или 1, что-то такое).

xterro ★★★★★
() автор топика
Ответ на: комментарий от ckotinko

Пробовал и configure, это событие реагирует на движение окна и изменение его разера, но у меня оно почему-то тоже не среагировало, думаю где-то что-то не переопределил, или может нужно было метод какой родительский вызвать... делал просто:

g_signal_connect(G_OBJECT(button_entry), "configure-event", G_CALLBACK(button_entry_configure), NULL)
В обработчике button_entry_configure также вызвал g_printf, запустил бинарник, подёргал форму со своим контролом, поизменял её размер(разер контрола также менялся), консоль чистая... хм, пожал плечами, и убрал этот код :)

xterro ★★★★★
() автор топика
Ответ на: комментарий от ckotinko

попробовал и просто configure, не работает, но в консоль ругнулась:

(be:12054): GLib-GObject-WARNING **: gsignal.c:2275: signal `configure' is invalid for instance `0x80a9820'

xterro ★★★★★
() автор топика
Ответ на: комментарий от xterro

а туплю я

The ::configure-event signal will be emitted when the size, position or stacking of the widget's window has changed.

To receive this signal, the GdkWindow associated to the widget needs to enable the GDK_STRUCTURE_MASK mask. GDK will enable this mask automatically for all new windows

ckotinko ☆☆☆
()
Ответ на: а туплю я от ckotinko

Тогда получается что оно у меня включено, но сыбытие не отрабатывает, ну да ладно :)

xterro ★★★★★
() автор топика
Ответ на: комментарий от xterro

Ну чего ты страдаешь?

10 строк на рабочий прототип из HBox'a: http://s39.radikal.ru/i085/1105/ea/13c359a4febb.png

import gtk

class ButtonEntry(gtk.HBox):
    __gsignals__ = {
        "expose-event": "override",
    }

    def __init__(self, entry, *args):
        gtk.HBox.__init__(self, *args)
        self.set_app_paintable(True)

        self.entry = entry
        entry.set_has_frame(False)
        self.pack_start(entry)

    def do_expose_event(self, event):
        self.entry.style.paint_shadow(event.window, gtk.STATE_ACTIVE, gtk.SHADOW_IN, event.area,
            self.entry, 'entry', self.allocation.x, self.allocation.y, self.allocation.width,
            self.allocation.height)

        gtk.HBox.do_expose_event(self, event)


window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.connect('destroy', gtk.main_quit)
window.set_border_width(5)

e = gtk.Entry()
be = ButtonEntry(e)
be.set_border_width(5)
be.pack_end(gtk.Button('...'))
window.add(be)

window.show_all()

gtk.main()

baverman ★★★
()
Ответ на: комментарий от baverman

Дык, такой вариант я пробовал в самом начале, когда только начинал всё это дело. Сейчас написал, как в вашем примере, получилось вот такое: http://www.imagepost.ru/?v=one_more.png

Вот expose, если раскоментить рисование тени, получается ещё страшнее(вокруг контрола всё заливается тенью) :))

static gint 

gtk_button_entry_expose(GtkWidget * widget, GdkEventExpose *event)

{

	/*

	gtk_paint_shadow(widget->style, 
						widget->window, 
						GTK_STATE_ACTIVE, 
						GTK_SHADOW_IN, 
						&event->area, 
						widget, 
						"entry", 
						widget->allocation.x,
						widget->allocation.y,
					widget->allocation.width,
					widget->allocation.height);*/

	GTK_WIDGET_CLASS(parent_class)->expose_event(widget, event);


return FALSE;
}

в init вынес:

static void 
gtk_button_entry_init(GtkButtonEntry * widget)

{
	widget->entry = gtk_entry_new();
	gtk_entry_set_has_frame(widget->entry, FALSE);
	gtk_widget_set_app_paintable(widget, TRUE);
	gtk_box_pack_start(GTK_BOX(widget), widget->entry, TRUE, TRUE, 0);
}
То, что у вас так выглядит, может это из-за темы? У меня выглядит так(по моей ссылке), явно не как нэйтив, и в линуксе и под виндой, и ещё, если изменять размер виджета, то кнопка увеличивается, а поле остаётся с постоянным размером.

xterro ★★★★★
() автор топика
Ответ на: комментарий от xterro

В gtk_paint_shadow() тебе надо передавать инстанцию GtkEntry, точнее сказать «то, для чего G_OBJECT_TYPE() вернет GTK_TYPE_ENTRY». А у тебя widget суется — это не знаю тут уже что, наверное GtkHBox. Я ж говорю *есть* разница при отрисовке, т.к. многие движки тем зачем-то смотрят в имя класса, а не в деталь, как задумано. Может на теме baverman'а оно и было бы одинакое, но код у него правильный.

GtkEntry *NATIVE = self->entry; gtk_paint_shadow(NATIVE->style, widget->window, GTK_STATE_ACTIVE, GTK_SHADOW_IN, &event->area, NATIVE, «entry», widget->allocation.x, widget->allocation.y, widget->allocation.width, widget->allocation.height);

arturpub ★★
()
Ответ на: комментарий от xterro

На обычном клирлуксе батон будет отличаться, хотя с первого взгляда можно и не уловить :) Нарисуй побольше, 200х200 в разных стейтах и увидишь. Я в свое время тоже все это поле перекопал, пришел вот к чему:

Есть понятие «внешнего вида контрола», этот код лежит в engine.so, который у каждого на вкус и цвет. Есть понятие «поведение виджета», этот код для всех стандартный. Связь между ними такова: движок при отрисовке смотрит на 1) стиль, т.е. хинты gtkrc, которые класс-зависимые, 2) класс виджета (==G_OBJECT_TYPE(...)), 3) детайл. То есть фактически связи через детайл почти нет, почти всегда есть жесткая привязка к классу.

Исходя из всей этой ситуации я пришел к выводу, что в GTK нет смысла наследоваться, т.к. это толком никаких плюсов не дает. Единственный выбор, который я делаю — это наследоваться от GtkContainer'а со товарищи, или же просто написать функцию, которая c помощью g_signal_connect() и g_object_set_data() сляпает композит внутри GtkEventBox'а, прицепит нужный функционал, и вернет его как будто это мой кастомный виджет.

arturpub ★★
()
Ответ на: комментарий от xterro

То, что у вас так выглядит, может это из-за темы?

Нет, как уже отметили, надо прикинуться GtkEntry в paint_shadow.

если изменять размер виджета, то кнопка увеличивается, а поле остаётся с постоянным размером.

Да, издержки HBox. Прототип #2

import gtk

class ButtonEntry(gtk.Container):
    __gsignals__ = {
        "size-request": "override",
        "size-allocate": "override",
        "expose-event": "override",
    }

    def __init__(self, entry, button):
        gtk.Container.__init__(self)
        self.set_has_window(False)
        self.set_redraw_on_allocate(True)
        self.set_app_paintable(True)

        self.entry = entry
        entry.set_has_frame(False)
        self.entry.set_parent(self)

        self.button = button
        self.button.set_parent(self)
        self.border = 3

    def do_size_request(self, req):
        w, h =  self.entry.size_request()
        req.width = w + h + self.border * 2
        req.height = h + self.border * 2

    def do_size_allocate(self, allocation):
        self.allocation = allocation

        x, y, w, h = allocation.x, allocation.y, allocation.width, allocation.height
        hh = h - self.border * 2
        ww = w - self.border * 2

        entry_alloc = gtk.gdk.Rectangle(x + self.border, y + self.border, ww - hh, hh)
        self.entry.size_allocate(entry_alloc)

        button_alloc = gtk.gdk.Rectangle(x + self.border + ww - hh, y + self.border, hh, hh)
        self.button.size_allocate(button_alloc)

    def do_expose_event(self, event):
        self.entry.style.paint_shadow(event.window, gtk.STATE_ACTIVE, gtk.SHADOW_IN, event.area,
            self.entry, 'entry', self.allocation.x, self.allocation.y, self.allocation.width,
            self.allocation.height)

        gtk.Container.do_expose_event(self, event)
        return True

    def do_forall(self, internal, callback, data):
        callback(self.entry, data)
        callback(self.button, data)

window = gtk.Window(gtk.WINDOW_TOPLEVEL)
window.connect('destroy', gtk.main_quit)
window.set_border_width(5)

be = ButtonEntry(gtk.Entry(), gtk.Button('...'))
window.add(be)

window.show_all()

gtk.main()

baverman ★★★
()
Ответ на: комментарий от baverman

Что самое интересное, я и GtkEntry «прикидывался», по разному попробовал:

   
   GtkButtonEntry * e = GTK_BUTTON_ENTRY(widget);
   gtk_paint_shadow(widget->style, 
                  widget->window, 
                  GTK_STATE_ACTIVE, 
                  GTK_SHADOW_IN, 
                  &event->area, 
                  e->entry, 
                  "entry", 
                  widget->allocation.x,
                  widget->allocation.y,
               widget->allocation.width,
               widget->allocation.height);

результат аналогичный. Вечером выложу сюда архив с сорсом, и заодно поиграюсь с темой, у меня используется «клирлукс». Потому как странно, код вроде одинаковый, и пробовал по всякому, а «результаты» не очень :)

xterro ★★★★★
() автор топика
Ответ на: комментарий от xterro

Запустил свой виджет на клеарлуксе тот же результат: paint_shadow заливает всё синим. Надо просто аккуратнее с размером GtkEntry обращаться и всё будет пучком.

baverman ★★★
()
Ответ на: комментарий от xterro

Блин, не могу потестить, среды нету, но на первый взгляд разница в аргументе state, см. [1]. Там кстати чуть ниже в случае интериор-фокуса что-то дорисовывается, тоже можно забрать )) Сорцы вообще хорошая вещь )

[1] http://www.koders.com/c/fid656267515125B7486ACE41475551665305D824F6.aspx?s=qu...

arturpub ★★
()
Ответ на: комментарий от arturpub

Там бинарник в архиве есть и уже готовые make и cmake файлы, если пересобрать захочется :) А сорсы я как раз параллельно читаю(с git.gnome.org), сейчас смотрю, что можно сделать с set_inner_border, чтобы косяков не было.

xterro ★★★★★
() автор топика
Ответ на: комментарий от baverman

Т.е на клеарлуксе косячит, а на той(первоначальной) теме всё нормально? Хмм... поэтому я и не стал делать его композитным, думаю отрисую кнопочку сам :)

xterro ★★★★★
() автор топика
Ответ на: комментарий от xterro

а на той(первоначальной) теме всё нормально?

Да, на моей текущей теме твой виджет работает нормально.

baverman ★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.