Как я могу отправить и получить сигнал, когда файл загружается в мое приложение GJS? - PullRequest
0 голосов
/ 03 февраля 2019

У меня есть приложение, которое должно открыть файл и соответственно обновить элементы пользовательского интерфейса.Я могу выбрать и открыть файл (и записать содержимое файла), но не могу сказать элементам пользовательского интерфейса обновить.

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

Я пытаюсь сделать что-то вроде этого: (в моей функции, которая прочитала файл с диска)

try {
                Signals.addSignalMethods(this);
                this.emit('update_ui', true);
              } catch(e) {
                print(e);
              }

(и в основном классе приложения)

Signals.addSignalMethods(this);
  this.connect('update_ui',() => {
    try {
      print('>>> updating UI');
      this.ui.updateUI();
    } catch (e) {
      print(e);
    }
  });

Я не получаю никаких ошибок при запуске приложения, но функция обновления никогда не вызывается.Как я могу получить сигнал для прохождения?

Вот код из файла main.js, который должен перехватить сигнал:

#!/usr/bin/gjs


Gio = imports.gi.Gio;
GLib = imports.gi.GLib;
Gtk = imports.gi.Gtk;
Lang = imports.lang;
Webkit = imports.gi.WebKit2;
Signals = imports.signals;
GObject = imports.gi.GObject;
Pango = imports.gi.Pango;

//
// add app folder to path
//

function getAppFileInfo() {
  let stack = (new Error()).stack,
    stackLine = stack.split('\n')[1],
    coincidence, path, file;

  if (!stackLine) throw new Error('Could not find current file (1)');

  coincidence = new RegExp('@(.+):\\d+').exec(stackLine);
  if (!coincidence) throw new Error('Could not find current file (2)');

  path = coincidence[1];
  file = Gio.File.new_for_path(path);
  return [file.get_path(), file.get_parent().get_path(), file.get_basename()];
}
const path = getAppFileInfo()[1];
imports.searchPath.push(path);

const myApp = new Lang.Class({
  Name: 'My Application',

  // Create the application itself
  _init: function() {
    this.application = new Gtk.Application();

    // Connect 'activate' and 'startup' signals to the callback functions
    this.application.connect('activate', Lang.bind(this, this._onActivate));
    this.application.connect('startup', Lang.bind(this, this._onStartup));
  },

  // Callback function for 'activate' signal presents windows when active
  _onActivate: function() {
    this._window.present();
  },

  // Callback function for 'startup' signal builds the UI
  _onStartup: function() {
    this._buildUI();
  },

  // Build the application's UI
  _buildUI: function() {

    // Create the application window
    this._window = new Gtk.ApplicationWindow({
      application: this.application,
      title: "My App",
      default_height: 200,
      default_width: 400,
      window_position: Gtk.WindowPosition.CENTER
    });

    //
    // menu bar
    //
    const Menubar = imports.lib.menubar;
    this._window.set_titlebar(Menubar.getHeader());

    Signals.addSignalMethods(this);
    this.connect('update_ui', () => {
      try {
        print('>>> updating UI');
        //this.ui.updateUI();
      } catch (e) {
        print(e);
      }
    });

    // Vbox to hold the switcher and stack.
    this._Vbox = new Gtk.VBox({
      spacing: 6
    });
    this._Hbox = new Gtk.HBox({
      spacing: 6,
      homogeneous: true
    });

    // const UI = imports.UI.UI;
    // this.ui = new UI.UIstack();
    // this.ui._buildStack();

    // this._Hbox.pack_start(this.ui._stack_switcher, true, true, 0);
    this._Vbox.pack_start(this._Hbox, false, false, 0);
    // this._Vbox.pack_start(this.ui._Stack, true, true, 0);

    // Show the vbox widget
    this._window.add(this._Vbox);

    // Show the window and all child widgets
    this._window.show_all();

  },

});

// Run the application
const app = new myApp();
app.application.run(ARGV);

и вот файл заголовка, который излучает сигнал:

const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const File = imports.lib.file;

const PopWidget = function(properties) {

  let label = new Gtk.Label({
    label: properties.label
  });
  let image = new Gtk.Image({
    icon_name: 'pan-down-symbolic',
    icon_size: Gtk.IconSize.SMALL_TOOLBAR
  });
  let widget = new Gtk.Grid();
  widget.attach(label, 0, 0, 1, 1);
  widget.attach(image, 1, 0, 1, 1);

  this.pop = new Gtk.Popover();
  this.button = new Gtk.ToggleButton();
  this.button.add(widget);
  this.button.connect('clicked', () => {
    if (this.button.get_active()) {
      this.pop.show_all();
    }
  });
  this.pop.connect('closed', () => {
    if (this.button.get_active()) {
      this.button.set_active(false);
    }
  });
  this.pop.set_relative_to(this.button);
  this.pop.set_size_request(-1, -1);
  this.pop.set_border_width(8);
  this.pop.add(properties.widget);
};

const getHeader = function() {

  let headerBar, headerStart, imageNew, buttonNew, popMenu, imageMenu, buttonMenu;

  headerBar = new Gtk.HeaderBar();
  headerBar.set_title("My App");
  headerBar.set_subtitle("Some subtitle text here");
  headerBar.set_show_close_button(true);

  headerStart = new Gtk.Grid({
    column_spacing: headerBar.spacing
  });

  // this.widgetOpen = new PopWidget({ label: "Open", widget: this.getPopOpen() });

  imageNew = new Gtk.Image({
    icon_name: 'document-open-symbolic',
    icon_size: Gtk.IconSize.SMALL_TOOLBAR
  });
  buttonNew = new Gtk.Button({
    image: imageNew
  });
  buttonNew.connect('clicked', () => {
    const opener = new Gtk.FileChooserDialog({
      title: 'Select a file'
    });
    opener.set_action(Gtk.FileChooserAction.OPEN);
    opener.add_button('open', Gtk.ResponseType.ACCEPT);
    opener.add_button('cancel', Gtk.ResponseType.CANCEL);
    const res = opener.run();
    if (res == Gtk.ResponseType.ACCEPT) {
      const filename = opener.get_filename();
      print(filename);

      const fileData = File.open(filename);
      print(JSON.stringify(fileData, null, 2));
      File.unRoll(fileData);
      // 
      // SHOULD SEND A SIGNAL
      //
      try {
        Signals.addSignalMethods(this);
        this.emit('update_ui', true);
      } catch (e) {
        print(e);
      }

      // this._window.ui.updateUI();
    }
    opener.destroy();
  });

  // headerStart.attach(this.widgetOpen.button, 0, 0, 1, 1);
  headerStart.attach(buttonNew, 1, 0, 1, 1);
  headerBar.pack_start(headerStart);

  popMenu = new Gtk.Popover();
  imageMenu = new Gtk.Image({
    icon_name: 'document-save-symbolic',
    icon_size: Gtk.IconSize.SMALL_TOOLBAR
  });
  buttonMenu = new Gtk.MenuButton({
    image: imageMenu
  });
  buttonMenu.set_popover(popMenu);
  popMenu.set_size_request(-1, -1);
  buttonMenu.set_menu_model(this.getMenu());

  headerBar.pack_end(buttonMenu);

  return headerBar;
};

const getPopOpen = function() {
  /* Widget popover */

  let widget = new Gtk.Grid(),
    label = new Gtk.Label({
      label: "Label 1"
    }),
    button = new Gtk.Button({
      label: "Other Documents ..."
    });

  button.connect('clicked', () => {
    this.widgetOpen.pop.hide();
    this.printText('Open other documents');
  });
  button.set_size_request(200, -1);

  widget.attach(label, 0, 0, 1, 1);
  widget.attach(button, 0, 1, 1, 1);
  widget.set_halign(Gtk.Align.CENTER);

  return widget;
};

const getMenu = function() {
  /* GMenu popover */

  let menu, section, submenu;

  menu = new Gio.Menu();

  section = new Gio.Menu();
  section.append("Save As...", 'app.saveAs');
  section.append("Save All", 'app.saveAll');
  menu.append_section(null, section);

  section = new Gio.Menu();
  submenu = new Gio.Menu();
  section.append_submenu('View', submenu);
  submenu.append("View something", 'app.toggle');
  submenu = new Gio.Menu();
  section.append_submenu('Select', submenu);
  submenu.append("Selection 1", 'app.select::one');
  submenu.append("Selection 2", 'app.select::two');
  submenu.append("Selection 3", 'app.select::thr');
  menu.append_section(null, section);

  section = new Gio.Menu();
  section.append("Close All", 'app.close1');
  section.append("Close", 'app.close2');
  menu.append_section(null, section);

  // Set menu actions
  let actionSaveAs = new Gio.SimpleAction({
    name: 'saveAs'
  });
  actionSaveAs.connect('activate', () => {
    const saver = new Gtk.FileChooserDialog({
      title: 'Select a destination'
    });
    saver.set_action(Gtk.FileChooserAction.SAVE);
    saver.add_button('save', Gtk.ResponseType.ACCEPT);
    saver.add_button('cancel', Gtk.ResponseType.CANCEL);
    const res = saver.run();
    if (res == Gtk.ResponseType.ACCEPT) {
      const filename = saver.get_filename();
      print(filename);
      const data = File.rollUp();
      File.save(filename, data);
      // let data = JSON.stringify(<FILE DATA>, null, '\t');
      // GLib.file_set_contents(filename, data);
    }
    saver.destroy();
  });
  APP.add_action(actionSaveAs);

  let actionSaveAll = new Gio.SimpleAction({
    name: 'saveAll'
  });
  actionSaveAll.connect('activate', () => {
    Gtk.FileChooserAction.OPEN
  });
  APP.add_action(actionSaveAll);

  let actionClose1 = new Gio.SimpleAction({
    name: 'close1'
  });
  actionClose1.connect('activate', () => {
    this.printText('Action close all');
  });
  APP.add_action(actionClose1);

  let actionClose2 = new Gio.SimpleAction({
    name: 'close2'
  });
  actionClose2.connect('activate', () => {
    this.printText('Action close');
  });
  APP.add_action(actionClose2);

  let actionToggle = new Gio.SimpleAction({
    name: 'toggle',
    state: new GLib.Variant('b', true)
  });
  actionToggle.connect('activate', (action) => {
    let state = action.get_state().get_boolean();
    if (state) {
      action.set_state(new GLib.Variant('b', false));
    } else {
      action.set_state(new GLib.Variant('b', true));
    }
    this.printText('View ' + state);
  });
  APP.add_action(actionToggle);

  let variant = new GLib.Variant('s', 'one');
  let actionSelect = new Gio.SimpleAction({
    name: 'select',
    state: variant,
    parameter_type: variant.get_type()
  });
  actionSelect.connect('activate', (action, parameter) => {
    let str = parameter.get_string()[0];
    if (str === 'one') {
      action.set_state(new GLib.Variant('s', 'one'));
    }
    if (str === 'two') {
      action.set_state(new GLib.Variant('s', 'two'));
    }
    if (str === 'thr') {
      action.set_state(new GLib.Variant('s', 'thr'));
    }
    this.printText('Selection ' + str);
  });
  APP.add_action(actionSelect);

  return menu;
};

это не класс, а статическая библиотека ... она импортируется файлом main.js, поэтому я думаю, что область действия будет такой же, но, возможно, нет ...

1 Ответ

0 голосов
/ 05 февраля 2019

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

const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;

var MyApp = GObject.registerClass({
    // This must be unique, although I don't believe you have to specify it
    GTypeName: 'MyApp',

    // GObject already have their own signal system, so invoking addSignalMethods()
    // will override the existing methods and could cause you problems. Here's a
    // simple example of how you'd add a GObject signal to a subclass; the resulting
    // callback would look like:
    //
    //     myapp.connect('update-ui', (application, bool) => { ... });
    Signals: {
        'update-ui': {
            param_types: [GObject.TYPE_BOOLEAN]
        },
    }
}, class MyApp extends Gtk.Application {

    _init(params) {
        // You could pass the regular params straight through
        //super._init(params);

        // Or setup your class to be initted without having to pass any args in your
        // constructor
        super._init({
            // This will also become your well-known name on DBus, with the matching
            // object path of /org/github/username/MyApp.
            //
            // GApplication framework will also use these for other things like any
            // GResource you bundle with your application.
            application_id: 'com.github.username.MyApp',
            // There are other flags you can set to allow your app to be a target for
            // "Open with...", but that's not what you're asking about today :)
            flags: Gio.ApplicationFlags.HANDLES_OPEN
        });

        // You can also do other setup , however it's important to note that
        // GApplication's are generally "single-instance" processes, so you want most
        // of that setup in ::startup which will only run if this is the primary
        // instance.
        //
        // On the other hand if you pass HANDLES_OPEN, for example, that signal/vfunc
        // will still be invoked.
        GLib.set_prgname(this.application_id);
        GLib.set_application_name('MyApp');
    }

    _buildUI() {
        // ...
    }

    // defining a virtual function usually means overriding the default handler for a
    // a signal. In this case, it's not much of an issue but for frequently emitted
    // signals it avoids "marshalling" the C values into JS values and back again.
    //
    // In other words, defining this function means it will be called as if  you
    // connected to the ::activate signal.
    vfunc_activate() {
        this._window.present();
    }

    // ::activate is a special case, but for most vfunc's is necessary to "chain up",
    // which really just means calling the super class's original function inside your
    // override.
    vfunc_startup() {
        // In ::startup we need to chain up first since GApplication does important
        // setup checks here
        super.vfunc_startup();

        // your stuff after
        this._buildUI();

        // In _buildUI() you correctly passed your GtkApplication as the application
        // property, which will keep the application running so long as that window
        // is open.
        //
        // If you wanted your application to stay open regardless, you call hold()
        this.hold();
    }

    vfunc_shutdown() {
        // ::shutdown on the other hand is the reverse; first we do our stuff, then
        // chain up to super class. This is a good time to do any cleanup you need
        // before the process exits.
        this._destroyUI();

        // chain up
        super.vfunc_shutdown();
    }
});

// For technical reasons, this is the proper way you should start your application
(new MyApp()).run([imports.system.programInvocationName].concat(ARGV));

Функция addSignalMethods () должна вызываться только один раз, но, как уже упоминалосьпереопределит существующие методы сигналов, и это может вызвать проблемы, если вы вызовете его для объекта GObject, для которого уже определены сигналы.Это в основном делает это:

addSignalMethods(obj) {
    obj.emit = function() {
        // custom signal code
    }
    ...

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

// SHOULD SEND A SIGNAL
//
try {
    Signals.addSignalMethods(this);
    this.emit('update_ui', true);
} catch (e) {
    print(e);
}

Причина, по которой это не работало длявы в том, что getHeaderbar() является функцией верхнего уровня в imports.lib.menubar, поэтому, когда вы вызываете addSignalMethods(this), this === imports.lib.menubar.Поэтому, чтобы поймать этот сигнал, вы должны были бы вызвать:

imports.lib.menubar.connect('update_ui', () => {});

Когда вы вызывали его в _buildUI():

Signals.addSignalMethods(this);
this.connect('update_ui', () => {
    try {
        print('>>> updating UI');
        //this.ui.updateUI();
    } catch (e) {
        print(e);
    }
});

this === MyApp, то есть вы сначала переопределили методы сигналов MyAppзатем подключается к себе.Импорт Signals довольно слабый, поэтому он не требует определения сигнала перед использованием;вы просто подключаетесь к тому, что хотите, и излучаете то, что хотите.Вот почему вы не получаете никаких ошибок или предупреждений.

Есть несколько способов решить вашу проблему:

// (1) Pick an object to emit and connect from and only add the signal methods once
Signals.addSignalMethods(headerBar);

// Since you're using an arrow function you can call this in the same place since it
// should still be in scope
headerBar.emit('update_ui', true);

// You can connect by grabbing the headerbar widget from your constructed window
this._window.get_titlebar().connect('update_ui', (headerBar, bool) => {});

// (2) Define a signal *on* your GtkApplication and use it as a relay

// Connect from a function in MyApp (so in `this.connect`, `this === MyApp`)
// To set `this` for the callback itself, use `Function.bind()`
this.connect('update-ui', this.ui.updateUI.bind(ui));

// You can always grab the primary instance of your GtkApplication (from anywhere)
// and emit the signal to invoke the MyApp.ui.updateUI() function
let myApp = Gio.Application.get_default();
myApp.emit('update-ui', true);

// (3) How I'd probably do it; just invoke the method directly
let myApp = Gio.Application.get_default();
myApp.ui.updateUI();
...