Русский
Русский
English
Статистика
Реклама

Gtk+

Пишем онлайн-радио на языке Vala

15.03.2021 06:18:42 | Автор: admin

Привет всем! В своем первом посте я хочу рассказать о создании простого радио на языке Vala. Я ни разу не программист, а скорее любитель и все свои разработки я писал на Java, но так как на компьютере использую GNU/Linux, а именно старый добрый Debian, да еще на GNOME, то подумал, а почему бы не попробовать написать что-нибудь под эту систему.

Подготовка

Сказано сделано! Писать решил в родном текстовом редакторе GEdit. Установил компилятор и пакет для разработки на GTK:

sudo apt install valac libgtk-3-dev

Также нам потребуется пакет GStreamer для разработчиков. Установим и его тоже:

sudo apt install libgstreamer1.0-dev

Идем дальше! В редакторе нужно включить поддержку встроенного терминала и отобразить нижнюю панель, где терминал и появится. Также для удобства следует включить дополнение слов, скобок и другие функции. Они все есть в списке модулей. Найти можно в настройках редактора. Очень удобные штуки. По сути они превращают обычный текстовый редактор в простейшую среду разработки.

Графический интерфейс

Файлы

Итак, редактор у нас готов. С чего начать? Конечно, с интерфейса! GUI будем писать ручками в редакторе. Но сначала о самом коде. У нас будет два файла. Один называется Application.vala и имеет такое содержание:

   namespace Raddiola {    public class Application : Gtk.Application {        public MainWindow app_window;    public Application() {        Object(flags: ApplicationFlags.FLAGS_NONE, application_id: "com.github.alexkdeveloper.raddiola");    }    protected override void activate() {        if(get_windows().length() > 0) {            app_window.present();            return;        }        app_window = new MainWindow(this);        app_window.show_all();    }    public static int main(string[] args) {        Gst.init (ref args);        var app = new Raddiola.Application();        return app.run(args);    }}}

Он предназначен для инициализации и запуска всех компонентов программы. Второй файл называется MainWindow.vala и содержит описание пользовательского интерфейса и логику приложения.

Внешний вид

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

Все элементы управления, включая кнопки "play" и "stop", я решил разместить в хидербаре. А список станций занял все оставшееся место. Вот так это все выглядит:

Для добавления и изменения станций имеется вторая страница, которая открывается при нажатии на соответствующую кнопку. Вот она:

Хидербар

После объявления всех необходимых компонентов и указания начальных свойств окна, создаем хидербар:

using Gtk;using Gst;namespace Raddiola {public class MainWindow : Gtk.ApplicationWindow {private Stack stack;private Box vbox_player_page;private Box vbox_edit_page;private dynamic Element player;private Gtk.ListStore list_store;private TreeView tree_view;private GLib.List<string> list;private Entry entry_name;private Entry entry_url;private Button back_button;private Button add_button;private Button delete_button;private Button edit_button;private Button play_button;private Button stop_button;private string directory_path;private string item;private int mode;    public MainWindow(Gtk.Application application) {        GLib.Object(application: application,                     title: "Raddiola",                     window_position: WindowPosition.CENTER,                     resizable: true,                     height_request: 500,                     width_request: 500,                     border_width: 10);    }        construct {        Gtk.HeaderBar headerbar = new Gtk.HeaderBar();headerbar.get_style_context().add_class(Gtk.STYLE_CLASS_FLAT);headerbar.show_close_button = true;set_titlebar(headerbar);back_button = new Gtk.Button ();    back_button.set_image (new Gtk.Image.from_icon_name ("go-previous-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    back_button.vexpand = false;add_button = new Gtk.Button ();    add_button.set_image (new Gtk.Image.from_icon_name ("list-add-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    add_button.vexpand = false;delete_button = new Gtk.Button ();    delete_button.set_image (new Gtk.Image.from_icon_name ("list-remove-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    delete_button.vexpand = false;edit_button = new Gtk.Button ();    edit_button.set_image (new Gtk.Image.from_icon_name ("document-edit-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    edit_button.vexpand = false;play_button = new Gtk.Button();    play_button.set_image (new Gtk.Image.from_icon_name ("media-playback-start-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    play_button.vexpand = false;stop_button = new Gtk.Button();    stop_button.set_image (new Gtk.Image.from_icon_name ("media-playback-stop-symbolic", Gtk.IconSize.SMALL_TOOLBAR));    stop_button.vexpand = false;  

Мы назначили иконки для кнопок в хидербаре. Теперь нам нужно добавить к кнопкам всплывающие подсказки, обработчики нажатия и добавить наконец эти кнопки в хидербар.

        back_button.set_tooltip_text("back");        add_button.set_tooltip_text("add station");        delete_button.set_tooltip_text("delete station");        edit_button.set_tooltip_text("edit station");        play_button.set_tooltip_text("play");        stop_button.set_tooltip_text("stop");        back_button.clicked.connect(on_back_clicked);        add_button.clicked.connect(on_add_clicked);        delete_button.clicked.connect(on_delete_dialog);        edit_button.clicked.connect(on_edit_clicked);        play_button.clicked.connect(on_play_station);        stop_button.clicked.connect(on_stop_station);        headerbar.pack_start(back_button);        headerbar.pack_start(add_button);        headerbar.pack_start(delete_button);        headerbar.pack_start(edit_button);        headerbar.pack_end(stop_button);        headerbar.pack_end(play_button);

Далее, с помощью методаset_widget_visible мы скрываем на время ненужные кнопки:

set_widget_visible(back_button,false);set_widget_visible(stop_button,false);

Код метода:

private void set_widget_visible (Gtk.Widget widget, bool visible) {         widget.no_show_all = !visible;         widget.visible = visible;  }

Стек

Теперь добавим стек, в который будем загружать отдельные страницы приложения. Всего страниц две. Сразу после создания стека загрузим в него список:

stack = new Stack();          stack.set_transition_duration (600);          stack.set_transition_type (StackTransitionType.SLIDE_LEFT_RIGHT);          add (stack);//добавили стек   list_store = new Gtk.ListStore(Columns.N_COLUMNS, typeof(string));//начали создавать список           tree_view = new TreeView.with_model(list_store);           var text = new CellRendererText ();           var column = new TreeViewColumn ();           column.pack_start (text, true);           column.add_attribute (text, "markup", Columns.TEXT);           tree_view.append_column (column);           tree_view.set_headers_visible (false);           tree_view.cursor_changed.connect(on_select_item);   var scroll = new ScrolledWindow (null, null);        scroll.set_policy (PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);        scroll.add (this.tree_view);   vbox_player_page = new Box(Orientation.VERTICAL,20);   vbox_player_page.pack_start(scroll,true,true,0);   stack.add(vbox_player_page);//добавили в стек контейнер со списком

Вторая страница

Теперь можно приступать к созданию второй страницы. Она содержит два текстовых поля и кнопку. В одно поле пользователь вводит название станции, а в другое ее URL. Поля содержат значки, при нажатии на которые поля можно очистить от содержимого.

entry_name = new Entry();        entry_name.set_icon_from_icon_name (Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic");        entry_name.icon_press.connect ((pos, event) => {        if (pos == Gtk.EntryIconPosition.SECONDARY) {              entry_name.set_text("");//очистка поля ввода названия станции           }        });        var label_name = new Label.with_mnemonic ("_Name:");        var hbox_name = new Box (Orientation.HORIZONTAL, 20);        hbox_name.pack_start (label_name, false, true, 0);        hbox_name.pack_start (entry_name, true, true, 0);        entry_url = new Entry();        entry_url.set_icon_from_icon_name (Gtk.EntryIconPosition.SECONDARY, "edit-clear-symbolic");        entry_url.icon_press.connect ((pos, event) => {        if (pos == Gtk.EntryIconPosition.SECONDARY) {              entry_url.set_text("");//очистка поля ввода URL           }        });        var label_url = new Label.with_mnemonic ("_URL:");        var hbox_url = new Box (Orientation.HORIZONTAL, 20);        hbox_url.pack_start (label_url, false, true, 0);        hbox_url.pack_start (entry_url, true, true, 0);        var button_ok = new Button.with_label("OK");        button_ok.clicked.connect(on_ok_clicked);        vbox_edit_page = new Box(Orientation.VERTICAL,20);        vbox_edit_page.pack_start(hbox_name,false,true,0);        vbox_edit_page.pack_start(hbox_url,false,true,0);        vbox_edit_page.pack_start(button_ok,false,true,0);        stack.add(vbox_edit_page);        stack.visible_child = vbox_player_page;//показываем первую страницу приложения

Логика

Методы для создания дефолтных станций и их показа

Создание GUI завершено. Дальше нам понадобится фабрика для создания объекта player, c помощью которого мы будем воспроизводить станции. Также нам необходима директория где будут хранится станции в виде текстовых файлов. При создании этой директории нужно создать в ней файлы станций по умолчанию и показать этот список пользователю.

 player = ElementFactory.make ("playbin", "play");//создаем нужный объект с помощью фабрики   directory_path = Environment.get_home_dir()+"/.stations_for_radio_app";   GLib.File file = GLib.File.new_for_path(directory_path);   if(!file.query_exists()){     try{        file.make_directory();//создали директорию     }catch(Error e){        stderr.printf ("Error: %s\n", e.message);     }     create_default_stations();//создание станций по умолчанию   }   show_stations();//показываем список станций }

Метод create_default_stations.Создаем два массива. В одном хранятся названия десяти станций, а в другом их URL. С помощью цикла создаем десять текстовых документов. Название документа это название станции, а содержимое документа ее URL.

private void create_default_stations(){          string[] name_station = {"NonStopPlay","Classical Music","Fip Radio","Jazz Legends","Joy Radio","Live-icy","Music Radio","Radio Electron","Dubstep","Trancemission"};          string[] url_station = {"http://stream.nonstopplay.co.uk/nsp-128k-mp3","http://stream.srg-ssr.ch/m/rsc_de/mp3_128","http://direct.fipradio.fr/live/fip-midfi.mp3","http://jazz128legends.streamr.ru/","http://airtime.joyradio.cc:8000/airtime_192.mp3","http://live-icy.gss.dr.dk:8000/A/A05H.mp3","http://ice-the.musicradio.com/CapitalXTRANationalMP3","http://radio-electron.ru:8000/128","http://air.radiorecord.ru:8102/dub_320","http://air.radiorecord.ru:8102/tm_320"};          for(int i=0;i<10;i++){            try {                 FileUtils.set_contents (directory_path+"/"+name_station[i], url_station[i]);              } catch (Error e) {                     stderr.printf ("Error: %s\n", e.message);             }          }   }

Метод show_stations:

private void show_stations () {           list_store.clear();           list = new GLib.List<string> ();            try {            Dir dir = Dir.open (directory_path, 0);            string? name = null;            while ((name = dir.read_name ()) != null) {                list.append(name);            }        } catch (FileError err) {            stderr.printf (err.message);        }         TreeIter iter;           foreach (string item in list) {               list_store.append(out iter);               list_store.set(iter, Columns.TEXT, item);//показываем список станций           }       }   

Сообщения пользователю и запуск/остановка воспроизведения

Для вывода сообщений пользователю в приложении используется метод alert. Вот его содержимое:

private void alert (string str){          var dialog_alert = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL, Gtk.MessageType.INFO, Gtk.ButtonsType.OK, str);          dialog_alert.set_title("Message");          dialog_alert.run();          dialog_alert.destroy();       }   

Запуск и остановка воспроизведения:

private void on_play_station(){ //воспроизведение         var selection = tree_view.get_selection();           selection.set_mode(SelectionMode.SINGLE);           TreeModel model;           TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               alert("Choose a station");               return;           }      string uri;        try {            FileUtils.get_contents (directory_path+"/"+item, out uri);        } catch (Error e) {            stderr.printf ("Error: %s\n", e.message);        }      player.uri = uri;      player.set_state (State.PLAYING);      set_widget_visible(play_button,false);      set_widget_visible(stop_button,true);   }   private void on_stop_station(){//остановка воспроизведения      player.set_state (State.READY);      set_widget_visible(play_button,true);      set_widget_visible(stop_button,false);   }   

Добавление и изменение станций

Нажатия на кнопки добавления и изменения станции:

private void on_add_clicked () {//к добавлению станции              stack.visible_child = vbox_edit_page;              set_buttons_on_edit_page();              mode = 1;              if(!is_empty(entry_name.get_text())){                    entry_name.set_text("");              }              if(!is_empty(entry_url.get_text())){                    entry_url.set_text("");              }  }   private void on_edit_clicked(){//к изменению станции         var selection = tree_view.get_selection();           selection.set_mode(SelectionMode.SINGLE);           TreeModel model;           TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               alert("Choose a station");               return;           }        stack.visible_child = vbox_edit_page;        set_buttons_on_edit_page();        mode = 0;        entry_name.set_text(item);//показываем название станции        string url;        try {            FileUtils.get_contents (directory_path+"/"+item, out url);        } catch (Error e) {            stderr.printf ("Error: %s\n", e.message);        }        entry_url.set_text(url);//показываем URL станции   }

Нажатие на кнопку ОК на второй странице приложения. По значению переменной modeопределяем какой режим обрабатывать. Есть два режима: режим добавления и режим изменения станции. Также присутствует логика не позволяющая добавить станцию с уже существующим названием или поменять название станции на уже имеющееся в списке.

private void on_ok_clicked(){         if(is_empty(entry_name.get_text())){            alert("Enter the name");                    entry_name.grab_focus();                    return;        }        if(is_empty(entry_url.get_text())){           alert("Enter the url");                   entry_url.grab_focus();                   return;        }        switch(mode){//выбор по значению переменной            case 0://изменение станции        GLib.File select_file = GLib.File.new_for_path(directory_path+"/"+item);        GLib.File edit_file = GLib.File.new_for_path(directory_path+"/"+entry_name.get_text().strip());        if (select_file.get_basename() != edit_file.get_basename() && !edit_file.query_exists()){                FileUtils.rename(select_file.get_path(), edit_file.get_path());                if(!edit_file.query_exists()){                    alert("Rename failed");                    return;                }                try {                 FileUtils.set_contents (edit_file.get_path(), entry_url.get_text().strip());              } catch (Error e) {                     stderr.printf ("Error: %s\n", e.message);            }            }else{                if (select_file.get_basename() != edit_file.get_basename()) {                    alert("A station with the same name already exists");                    entry_name.grab_focus();                    return;                }                try {                 FileUtils.set_contents (edit_file.get_path(), entry_url.get_text().strip());              } catch (Error e) {                     stderr.printf ("Error: %s\n", e.message);             }            }            show_stations();            break;            case 1://добавление станции    GLib.File file = GLib.File.new_for_path(directory_path+"/"+entry_name.get_text().strip());        if(file.query_exists()){            alert("A station with the same name already exists");            entry_name.grab_focus();            return;        }        try {            FileUtils.set_contents (file.get_path(), entry_url.get_text().strip());        } catch (Error e) {            stderr.printf ("Error: %s\n", e.message);        }        if(!file.query_exists()){           alert("Add failed");           return;        }else{           show_stations();        }        break;      }      on_back_clicked();   }

Удаление станций и другие методы

Для удаления станций применяется такой метод:

private void on_delete_dialog(){       var selection = tree_view.get_selection();           selection.set_mode(SelectionMode.SINGLE);           TreeModel model;           TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               alert("Choose a station");               return;           }           GLib.File file = GLib.File.new_for_path(directory_path+"/"+item);         var dialog_delete_station = new Gtk.MessageDialog(this, Gtk.DialogFlags.MODAL,Gtk.MessageType.QUESTION, Gtk.ButtonsType.OK_CANCEL, "Delete station "+file.get_basename()+" ?");         dialog_delete_station.set_title("Question");         Gtk.ResponseType result = (ResponseType)dialog_delete_station.run ();         dialog_delete_station.destroy();         if(result==Gtk.ResponseType.OK){         FileUtils.remove (directory_path+"/"+item);         if(file.query_exists()){            alert("Delete failed");         }else{             show_stations();//показываем список станций после удаления         }      }   }

Для отображения кнопок в хидербаре на разных страницах используются следующие методы:

   private void set_buttons_on_player_page(){//кнопки на странице списка       set_widget_visible(back_button,false);       set_widget_visible(add_button,true);       set_widget_visible(delete_button,true);       set_widget_visible(edit_button,true);   }   private void set_buttons_on_edit_page(){//кнопки на странице изменения/добавления       set_widget_visible(back_button,true);       set_widget_visible(add_button,false);       set_widget_visible(delete_button,false);       set_widget_visible(edit_button,false);   }

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

private void on_select_item () {           var selection = tree_view.get_selection();           selection.set_mode(SelectionMode.SINGLE);           TreeModel model;           TreeIter iter;           if (!selection.get_selected(out model, out iter)) {               return;           }           TreePath path = model.get_path(iter);           var index = int.parse(path.to_string());           if (index >= 0) {               item = list.nth_data(index);//записываем значение позиции в списке в переменную           }       }

Следующий код возвращает пользователя назад к списку станций:

private void on_back_clicked(){       stack.visible_child = vbox_player_page;       set_buttons_on_player_page();   }

Для определения пустоты текстового поля используется метод is_empty:

private bool is_empty(string str){        return str.strip().length == 0;      }

Перечислитель для списка:

private enum Columns {           TEXT, N_COLUMNS       }

Компиляция и запуск

Чтобы скомпилировать приложение нужно в терминале перейти в директорию исходников программы и ввести такую команду:

valac --pkg gtk+-3.0 --pkg gstreamer-1.0 Application.vala MainWindow.vala

Для запуска приложения командуем:

./Application

Ссылка на репозиторий GitHub: https://github.com/kalexal-kaa/gtk-radio

Ссылка на SourceForge: https://sourceforge.net/projects/gtk-radio/

Вот пожалуй и все! Если пост понравится, то напишу и про другие свои разработки.

Подробнее..

GTK Как выглядит первый запуск анализатора в цифрах

04.01.2021 10:13:21 | Автор: admin

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

Введение

GTK кроссплатформенная библиотека элементов интерфейса. Недавно состоялся релиз GTK 4, что стало хорошим инфоповодом изучить качество кода проекта с помощью статического анализатора кода PVS-Studio. Такая деятельность для нас является регулярной, и нам часто приходится настраивать анализатор с нуля на многих проектах перед исследованием качества кода. В этой заметке я поделюсь опытом быстрой настройки PVS-Studio на C++ проекте.

Анализ GTK

Первые результаты

Получаем свой первый отчёт анализатора и видим следующие результаты для диагностик общего назначения:

4 (Fails) + 1102 (High) + 1159 (Medium) + 3093 (Low) = 5358 предупреждений.

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

Исключаем директории

Рассмотрим такое предупреждение:

V530 [CWE-252] The return value of function 'g_strrstr_len' is required to be utilized. strfuncs.c 1803

/* Testing functions bounds */static voidtest_bounds (void){  ....  g_strrstr_len (string, 10000, "BUGS");  g_strrstr_len (string, 10000, "B");  g_strrstr_len (string, 10000, ".");  g_strrstr_len (string, 10000, "");  ....}

Это код тестов, причём не относящихся непосредственно к GTK, поэтому составляем список директорий для исключения из анализа и перезапускаем PVS-Studio.

При следующем запуске из анализа будут исключены следующие директории:

gtk/_build/gtk/subprojects/gtk/tests/gtk/testsuite/

Открываем отчёт и получаем следующий результат:

2 (Fails) + 819 (High) + 461 (Medium) + 1725 (Low) = 3007 предупреждений.

Ещё один положительный эффект, который мы получили после такой настройки, это ускорение анализа.

Исключаем макросы

Макросы, пожалуй, - одна из основных причин аномального количества срабатываний у некоторых диагностик. Поверхностно смотрим наш отчёт и замечаем множество подобных предупреждений:

V501 There are identical sub-expressions '* (& pipe->ref_count)' to the left and to the right of the '^' operator. gdkpipeiostream.c 65

static GdkIOPipe *gdk_io_pipe_ref (GdkIOPipe *pipe){  g_atomic_int_inc (&amp;pipe->ref_count);  return pipe;}

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

#V501//-V:g_atomic_int_:501#V547//-V:GTK_IS_:547//-V:GDK_IS_:547//-V:G_IS_:547//-V:G_VALUE_HOLDS:547#V568//-V:g_set_object:568

Всего несколько строчек, которые покрывают большинство проблемных макросов для V501, V547 и V568.

Смотрим результат:

2 (Fails) + 773 (High) + 417 (Medium) + 1725 (Low) = 2917 предупреждений.

Отключаем диагностики

Некоторые диагностики изначально выдают неподходящие предупреждения для конкретного проекта. Рассмотрим предупреждение V1042:

V1042 [CWE-1177] This file is marked with copyleft license, which requires you to open the derived source code. main.c 12

Это очень полезная диагностика для проектов с закрытым кодом, чтобы случайно не включить в них код с недопустимой лицензией. Но для GTK это неинтересная диагностика, поэтому отключим её и получим скорректированный результат:

2 (Fails) + 164 (High) + 417 (Medium) + 1725 (Low) = 2308 предупреждений.

Изучаем фейлы

В проекте имеются 2 предупреждения типа Fails:

  • V002 Some diagnostic messages may contain incorrect line number in this file. gdkrectangle.c 1

  • V002 Some diagnostic messages may contain incorrect line number in this file. gdktoplevelsize.c 1

Эта диагностика предупреждает, что срабатывания на этих файлах могут указывать не на те строки кода, которые нужно. Обычно разница составляет 1-2 строки, и происходит это из-за неверного раскрытия макросов компилятором. По нашему опыту, чаще всего за этим был замечен компилятор MSVC.

Эти предупреждения можно просто проигнорировать.

Выводы

Итоговый результат такой:

164 (High) + 417 (Medium) + 1725 (Low) = 2306 предупреждений.

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

V501 There are identical sub-expressions 'G_PARAM_EXPLICIT_NOTIFY' to the left and to the right of the '|' operator. gtklistbase.c 1151

static voidgtk_list_base_class_init (GtkListBaseClass *klass){  ....  properties[PROP_ORIENTATION] =    g_param_spec_enum ("orientation",                       P_("Orientation"),                       P_("The orientation of the orientable"),                       GTK_TYPE_ORIENTATION,                       GTK_ORIENTATION_VERTICAL,                       G_PARAM_READWRITE |                       G_PARAM_EXPLICIT_NOTIFY |  // &lt;=                       G_PARAM_EXPLICIT_NOTIFY);  // &lt;=  ....}

Это отличный результат! И показатели других диагностик тоже значительно выросли. Мизерными настройками удалось уменьшить отчёт анализатора на целых 57%. Соответственно, показатель верных предупреждений к ложным тоже значительно вырос.

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

А теперь время передать эстафету моему коллеге Андрею Карпову.

Примечание Андрея Карпова

Даже такая простая и быстрая фильтрация результатов, описанная в этой статье, существенно облегчает работу с отчётом. Например, взяв этот отчёт, я в течение одного вечера смог бегло его просмотреть и выписать фрагменты кода с ошибками, в количестве достаточном для написания статьи. Собственно, её написанием я и займусь на новогодних праздниках.

Конечно, моя задача проще и сильно отличается от процесса настройки и внедрения анализатора в реальный проект. Мне достаточно поверхностно пробежаться по списку предупреждений и выписать явные ошибки, игнорируя ложные срабатывания или непонятные предупреждения в сложных участках кода. При реальном использовании понадобится больше времени, чтобы настроить анализатор, точечно подавить ложные срабатывания, улучшить макросы и так далее. Но на самом деле, это не страшно. Например, в статье про проверку проекта EFL Core Libraries я показал, что можно достаточно легко настроить анализатор, чтобы он выдавал всего 10-15% ложных предупреждений. Согласитесь, неплохо, когда на каждые 1-2 ложных срабатывания вы исправляете 8-9 ошибок.

Ну и не забывайте, что вам всегда доступен механизм массового подавления срабатываний анализатора. Это позволяет начать быстро использовать анализатор даже в большом проекте. Все предупреждения объявляются техническим долгом и пока не показываются. И команда начинает работать только с предупреждениями, относящимися к новому или изменённому коду. Подробнее про это предлагаю почитать в статье "Как внедрить статический анализатор кода в legacy проект и не демотивировать команду".

Спасибо за внимание и приходите через пару недель читать статью про найденные ошибки.

Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Svyatoslav Razmyslov. GTK: The First Analyzer Run in Figures.

Подробнее..

Gtk, OpenGL и все-все-все

26.05.2021 18:20:11 | Автор: admin

Не так давно мне пришлось столкнутся с использованием OpenGL
в Gtkmm версии 3. В данной статье я постараюсь изложить детали имплементации стандартного виджета для OpenGL-графики.


Gtk::GLArea


Быстрый поиск по фразе Gtkmm+OpenGL выдает кучу примеров для Gtk::GLArea стандартного виджета для OpenGL. Он имеет довольно гибкий интерфейс, а так же следующие возможности:


  • опциональная поддержка прозрачности самого виджета,
  • настройка буфера трафарета и глубины,
  • сигналы для инициализации и освобождения контекста OpenGL.

В обычном случае, все команды рисования располагаются в обработчике сигнала signal_draw(),
но есть возможность создать сцену в процессе инициализации, и она будет отображаться
даже при перерисовке окна.


Копаем глубже


Gtk::GLArea содержит некоторое количество опциональных возможностей, которые
будут осложнять изложение, поэтому ссылаться я буду на созданную мной
упрощенную версию OpenGLWidget.


В целом, жизненный цикл виджета состоит из нескольких этапов:


  • Инициализация
  • Повторяющийся вызов функции отрисовки
  • Утилизация

Рассмотрим более подробно первые два пункта.


Инициализация


За процесс инициализации отвечает метод OpenGLWidget::on_realize, а схема показана на рисунке ниже:



Для перехватывания событий необходимо создать Gdk::Window. Так же наличие
у виджета своего экземпляра окна позволяет рисовать на его поверхности.
Код, который создает Gdk::Window:


GdkWindowAttr attributes;memset(&attributes, 0, sizeof(attributes));Gtk::Allocation allocation = get_allocation();//Set initial position and size of the Gdk::Window:attributes.x = allocation.get_x();attributes.y = allocation.get_y();attributes.width = allocation.get_width();attributes.height = allocation.get_height();attributes.event_mask = get_events ();attributes.window_type = GDK_WINDOW_CHILD;attributes.wclass = GDK_INPUT_OUTPUT;m_refGdkWindow = Gdk::Window::create(get_parent_window(), &attributes, GDK_WA_X | GDK_WA_Y);set_window(m_refGdkWindow);register_window(m_refGdkWindow);

Здесь задаются атрибуты, такие как тип, класс, позиция, ширина и высота.
Так же важно вызвать Gtk::Widget::set_window, чтобы передать виджету информацию о сконструированном Gdk::Window.
Метод Gtk::Widget::register_window делает возможным получение событий виджетом от указанного окна.


Gdk::GLContext является оберткой над контекстом OpenGL. Его создание иллюстрирует следующий код:


m_Context = m_refGdkWindow->create_gl_context();if(!m_Context->realize()){      std::cerr << "Context realize failed" << std::endl;      m_Context.reset();      return;}

Перед выполнением любых команд OpenGL необходимо вызвать Gdk::GLContext::make_current, как это сделано перед инициализацией Framebuffer и Renderbuffer:


m_Context->make_current();glGenFramebuffersEXT (1, &frame_buffer);glGenRenderbuffersEXT (1, &render_buffer);

Вывод на экран


Здесь необходимо сделать небольшое отступление. Дело в том, что виджет, как таковой, не
имеет непосредственного доступа к внутреннему буферу окна операционной системы и не может в него рисовать.


Поэтому Gtk::GLArea создает связку Framebuffer и Renderbuffer или текстуры.
Все команды OpenGL выполняются над текстурой или Renderbuffer, формируя итоговое
изображение. Далее, вызов функции gdk_cairo_draw_from_gl с необходимыми параметрами
сохраняет его в буфер окна ОС, после оно будет показано пользователю. В OpenGLWidget
используется связка Framebuffer и Renderbuffer.


Последовательность шагов для вывода на экран показана на диаграмме:



Все операции с графикой выполняются в имплементации Gtk::Widget::on_draw OpenGLWidget::on_draw.


Вот код, иллюстрирующий как привязываются буферы:


m_Context->make_current();auto scale = get_scale_factor ();m_BufferDimensions = {  scale,  get_allocated_width () * scale,  get_allocated_height () * scale};glBindRenderbuffer (GL_RENDERBUFFER, render_buffer);glRenderbufferStorage (GL_RENDERBUFFER, GL_RGB8, m_BufferDimensions->width, m_BufferDimensions->height);glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, frame_buffer);glFramebufferRenderbufferEXT (GL_FRAMEBUFFER_EXT,     GL_COLOR_ATTACHMENT0_EXT,    GL_RENDERBUFFER_EXT, render_buffer);glViewport (0, 0, m_BufferDimensions->width, m_BufferDimensions->height );

Здесь необходимо отметить сохранение параметров области вывода в переменную m_BufferDimensions, это нужно для дальнейшего вызова gdk_cairo_draw_from_gl.


Перед вызовом пользовательского колбэка и gdk_cairo_draw_from_gl необходимо проверить статус Framebuffer, и если он не готов, то пропустить эти инструкции. Их выполнение над таким Framebuffer будет ошибочным.


Вызов пользовательского клолбэка осуществляется с помощью механизма сигналов библиотеки sigc:


m_DrawSignal.emit();

Для вывода сформированного изображения из Renderbuffer необходимо выполнить инструкцию:


gdk_cairo_draw_from_gl (cr->cobj(),                                m_refGdkWindow->gobj(),                                render_buffer,                                GL_RENDERBUFFER,                                m_BufferDimensions->scale, 0, 0,                                 m_BufferDimensions->width,                                m_BufferDimensions->height);

Заключительный этап отвязываем буферы:


m_Context->make_current();glBindRenderbuffer (GL_RENDERBUFFER, 0);glBindFramebufferEXT (GL_FRAMEBUFFER_EXT, 0);m_Context->clear_current();

m_Context->make_current() здесь важен, потому что gdk_cairo_draw_from_gl может поменять текущий контекст.


Handmade Cairo::Context


В заключении рассмотрим вопрос явного, ручного создания Cairo::Context.


Его получает в качестве параметра обработчик сигнала signal_draw() или
метод Gtk::Widget::on_draw. Они вызываются, если есть потребность перерисовать контент виджета.


Создать такой контекст можно функцией Gdk::Window::begin_draw_frame, а в завершении
вызвать Gdk::Window::end_draw_frame.


Пример, как работает эта связка:


void OpenGLWidget::draw_content(){    if(m_refGdkWindow && m_refGdkWindow->is_visible())    {        auto region = m_refGdkWindow->get_visible_region();       /*1*/        auto context = m_refGdkWindow->begin_draw_frame(region);  /*2*/        draw(context->get_cairo_context());                       /*3*/        m_refGdkWindow->end_draw_frame(context);                  /*4*/    }}

Здесь m_refGdkWindow это Gdk::Window связанный с виджетом. Перед созданием контекста
необходимо получить параметры области для рисования (выражение 1). Далее, вызывается Gdk::Window::begin_draw_frame
для конструирования Cairo::Context для указанной области (выражение 2). Для формирования
контента вызываем Gtk::Widget::draw (выражение 3), которая передаст управление либо имплементации Gtk::Widget::on_draw,
либо вызовет обработчик сигнала signal_draw(). Сообщаем о завершении процедуры рисования с помощью Gdk::Window::end_draw_frame
(выражение 4).

Подробнее..
Категории: C++ , Gtk+ , Opengl , Gtkmm

Автоматический переводчик на PythonGTK3. Альтернатива Яндексу

06.09.2020 06:06:16 | Автор: admin
Ну вот и пришел долгожданный конец халяве(статья).

Честно говоря, было немного обидно. Вот чего им не хватает!

Я, разумеется, начал искать выход для себя и друзей. И нашел.

Этим выходом стала библиотека translators(Яндекс она тоже умеет).
В целом, код остался прежним, как и в предыдущей статье, немного изменилась логика.

Убрано было все, что касается Яндекс, добавлены библиотеки langdetect и translators. Первая для определения языка, ибо без доступа к API пришлось бы делать это вручную. Вторая, соответственно, модуль доступа к гугло-переводчику посредством urllib и requests.
Вот, собственно, все новшества:
................from langdetect import detectimport translators as ts................indetect = detect(clip())def definition():if indetect == 'ru':        langout = 'en'    else:        langout = 'ru'    return langoutdef translate():    output = []    output = ts.google(clip(), to_language=definition(), if_use_cn_host=True)    return output................


Так же был изменен файл ~/.local/lib/python3.8/site-packages/translators/apis.py
53 #logger.add(sys.stdout, format='[{time:HH:mm:ss}] <lvl>{message}</lvl>', level='INFO')120 #sys.stderr.write(f'Using {data.get("country")} server backend.\n')144 self.cn_host_url = 'https://translate.google.ru'151 self.output_zh = 'ru-RU'

Ну вот совсем мне не нужен вывод отладочной информации, строки 53 и 120, а так же умолчальный сервер и вывод в 144 и 151 изменен с китайского на русский.

Проект на github.

P.S: Переводчик от Google, как оказалось, справляется со своим предназначением лучше яндексовского.
Подробнее..
Категории: Python , Яндекс , Python 3 , Gtk+ , Google translate

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru