init multi-window implementation

This commit is contained in:
yggverse 2024-07-05 22:04:26 +03:00
parent 6f99b36a44
commit 847a0fb01d
45 changed files with 904 additions and 833 deletions

View file

@ -0,0 +1,47 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser;
use \Yggverse\Yoda\Entity\Browser\Container\Tab;
class Container
{
public \GtkBox $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser $browser;
// Requirements
public \Yggverse\Yoda\Entity\Browser\Container\Tab $tab;
public function __construct(
\Yggverse\Yoda\Entity\Browser $browser
) {
// Init dependency
$this->browser = $browser;
// Init container
$this->gtk = new \GtkBox(
\GtkOrientation::VERTICAL
);
// Init tab
$this->tab = new Tab(
$this
);
$this->gtk->pack_start(
$this->tab->gtk,
true,
true,
0
);
}
public function refresh()
{
// @TODO
}
}

View file

@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container;
class Tab
{
public \GtkNotebook $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser\Container $container;
// Defaults
private bool $_reorderable = true;
private bool $_scrollable = true;
public function __construct(
\Yggverse\Yoda\Entity\Browser\Container $container
) {
// Init dependency
$this->container = $container;
// Init container
$this->gtk = new \GtkNotebook;
$this->gtk->set_scrollable(
$this->_scrollable
);
// Init previous session @TODO
$this->append();
$this->append();
// Connect events
$this->gtk->connect(
'switch-page',
function (
\GtkNotebook $entity,
\GtkWidget $child,
int $position
) {
$this->container->browser->header->setTitle(
$entity->get_tab_label(
$child
)->get_text()
);
}
);
}
public function append(
?string $request = null,
bool $focus = true
): void
{
$page = new \Yggverse\Yoda\Entity\Browser\Container\Tab\Page(
$this
);
if ($request)
{
$page->navbar->request->setValue(
$request
);
$page->content->update();
}
$this->gtk->append_page(
$page->gtk,
$page->title->gtk
);
$this->gtk->set_tab_reorderable(
$page->gtk,
$this->_reorderable
);
if ($focus)
{
// Focus on appended tab
$this->gtk->set_current_page(
$this->gtk->page_num(
$page->gtk
)
);
// Update application title
$this->container->browser->header->setTitle(
$page->title->gtk->get_text()
);
}
$this->gtk->show_all();
}
}

View file

@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Title;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Content;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Statusbar;
class Page
{
public \GtkBox $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser\Container\Tab $tab;
// Requirements
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Title $title;
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar $navbar;
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Content $content;
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Statusbar $statusbar;
public function __construct(
\Yggverse\Yoda\Entity\Browser\Container\Tab $tab
) {
// Init dependencies
$this->tab = $tab;
// Init container
$this->gtk = new \GtkBox(
\GtkOrientation::VERTICAL
);
// Init title
$this->title = new Title(
$this
);
// Init navbar
$this->navbar = new Navbar(
$this
);
$this->gtk->add(
$this->navbar->gtk
);
// Init content
$this->content = new Content(
$this
);
$this->gtk->pack_start(
$this->content->gtk,
true,
true,
0
);
// Init statusbar
$this->statusbar = new Statusbar(
$this
);
$this->gtk->add(
$this->statusbar->gtk
);
}
public function refresh(): void
{
$this->navbar->refresh();
$this->content->refresh();
$this->statusbar->refresh();
}
public function update(
bool $history = true
): void
{
// @TODO navbar
$this->content->update(
$history
);
}
}

View file

@ -0,0 +1,288 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Content\Data;
class Content
{
public \GtkScrolledWindow $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page $page;
// Requirements
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Content\Data $data;
// Defaults
private int $_margin = 8;
public function __construct(
\Yggverse\Yoda\Entity\Browser\Container\Tab\Page $page
) {
$this->page = $page;
// Init container
$this->gtk = new \GtkScrolledWindow;
$this->gtk->set_margin_start(
$this->_margin
);
$this->gtk->set_margin_end(
$this->_margin
);
// Init label
$this->data = new Data(
$this
);
$this->gtk->add(
$this->data->gtk
);
}
public function refresh()
{
// @TODO
}
public function update(
bool $history = true
): void
{
// Parse address
$address = new \Yggverse\Net\Address(
$this->page->navbar->request->gtk->get_text()
);
// Update title
$this->page->title->setValue(
$address->getHost()
);
if ($history)
{
// Remember address in the navigation memory
$this->page->navbar->history->add(
$address->get()
);
// Refresh history in database
$this->page->tab->container->browser->database->renewHistory(
$address->get(),
// @TODO title
);
}
// Update statusbar indicator
$this->page->statusbar->setValue(
'Loading...'
);
// Detect protocol
switch ($address->getScheme())
{
case 'file':
if (file_exists($address->getPath()) && is_readable($address->getPath()))
{
switch ($address->getPath())
{
case is_dir($address->getPath()):
// @TODO build fs listing
break;
case str_ends_with($address->getPath(), '.gmi'):
$title = null;
$this->data->setGemtext(
file_get_contents( // @TODO format relative links
$address->getPath()
),
$title
);
if ($title) // detect title by document h1
{
$this->page->title->setValue(
$title
);
}
$this->page->statusbar->setValue(
null
);
break;
default:
$this->page->title->setValue(
'Oops!'
);
$this->data->setPlain(
'File extension not supported'
);
$this->page->statusbar->setValue(
null
);
}
}
else
{
$this->page->title->setValue(
'Failure'
);
$this->data->setPlain(
'Could not open file'
);
$this->page->statusbar->setValue(
'Resource not found or not readable'
);
}
break;
case 'nex':
// @TODO
break;
case 'gemini':
$request = new \Yggverse\Gemini\Client\Request(
$address->get()
);
$response = new \Yggverse\Gemini\Client\Response(
$request->getResponse()
);
if (20 === $response->getCode())
{
switch (true)
{
case str_contains($response->getMeta(), 'text/gemini'):
$title = null;
$this->data->setGemtext(
$response->getContainer(),
$title
);
if ($title) // detect title by document h1
{
$this->page->title->setValue(
$title
);
}
break;
default:
$this->data->setPlain(
$response->getContainer()
);
}
$this->page->statusbar->setValue(
$response->getMeta()
);
}
else
{
$this->page->title->setValue(
'Failure'
);
$this->data->setPlain(
'Resource not available!'
);
$this->page->statusbar->setValue(
sprintf(
'code %d',
intval(
$response->getCode()
)
)
);
}
break;
case null:
// Try gemini protocol
$address = new \Yggverse\Net\Address(
sprintf(
'gemini://%s',
trim(
$this->page->navbar->request->gtk->get_text()
)
)
);
// Is hostname request
if (filter_var(
$address->getHost(),
FILTER_VALIDATE_DOMAIN,
FILTER_FLAG_HOSTNAME
)
) {
$this->page->navbar->request->setValue(
$address->get()
);
}
// Is search request
else
{
$this->page->navbar->request->setValue(
sprintf(
'gemini://tlgs.one/search?%s', // @TODO custom provider
urlencode(
$this->page->navbar->request->gtk->get_text()
)
)
);
}
$this->update();
return;
default:
$this->page->title->setValue(
'Oops!'
);
$this->data->setPlain(
'Protocol not supported!'
);
$this->page->statusbar->setValue(
null
);
}
$this->gtk->show_all();
}
}

View file

@ -0,0 +1,243 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Content;
use \Yggverse\Gemtext\Document;
use \Yggverse\Net\Address;
class Data
{
public \GtkLabel $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Content $content;
// Defaults
private string $_value = '';
public function __construct(
\Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Content $content
) {
// Init dependency
$this->content = $content;
// Init markup label
$this->gtk = new \GtkLabel;
$this->gtk->set_use_markup(
true
);
$this->gtk->set_selectable(
true
);
$this->gtk->set_line_wrap(
true
);
$this->gtk->set_xalign(
0
);
$this->gtk->set_yalign(
0
);
$this->setPlain(
$this->_value
);
$this->gtk->connect(
'activate-link',
function(
\GtkLabel $label,
string $href
) {
$this->content->page->navbar->request->setValue(
$this->_url(
$href
)
);
$this->content->page->update();
}
);
}
public function setPlain(
string $value
): void
{
$this->gtk->set_text(
$value
);
}
public function setGemtext(
string $value,
string | null &$title = null
): void
{
$document = new Document(
$value
);
$line = [];
foreach ($document->getEntities() as $entity)
{
switch (true)
{
case $entity instanceof \Yggverse\Gemtext\Entity\Code:
if ($entity->isInline())
{
$line[] = sprintf(
'<tt>%s</tt>',
htmlspecialchars(
$entity->getAlt()
)
);
}
else
{
// @TODO multiline
}
break;
case $entity instanceof \Yggverse\Gemtext\Entity\Header:
switch ($entity->getLevel())
{
case 1: // #
$line[] = sprintf(
'<span size="xx-large">%s</span>',
htmlspecialchars(
$entity->getText()
)
);
// Find and return document title by first # tag
if (empty($title))
{
$title = $entity->getText();
}
break;
case 2: // ##
$line[] = sprintf(
'<span size="x-large">%s</span>',
htmlspecialchars(
$entity->getText()
)
);
break;
case 3: // ###
$line[] = sprintf(
'<span size="large">%s</span>',
htmlspecialchars(
$entity->getText()
)
);
break;
default:
throw new \Exception;
}
break;
case $entity instanceof \Yggverse\Gemtext\Entity\Link:
$line[] = sprintf(
'<a href="%s" title="%s">%s</a>',
$this->_url(
$entity->getAddress()
),
htmlspecialchars(
$entity->getAddress()
),
htmlspecialchars(
$entity->getAlt() ? $entity->getAlt()
: $entity->getAddress() // @TODO date
)
);
break;
case $entity instanceof \Yggverse\Gemtext\Entity\Listing:
$line[] = sprintf(
'* %s',
htmlspecialchars(
$entity->getItem()
)
);
break;
case $entity instanceof \Yggverse\Gemtext\Entity\Quote:
$line[] = sprintf(
'<i>%s</i>',
htmlspecialchars(
$entity->getText()
)
);
break;
case $entity instanceof \Yggverse\Gemtext\Entity\Text:
$line[] = htmlspecialchars(
$entity->getData()
);
break;
default:
throw new \Exception;
}
}
$this->gtk->set_markup(
implode(
PHP_EOL,
$line
)
);
}
private function _url(
string $link
): ?string
{
$address = new Address(
$link
);
if ($address->isRelative())
{
$address->toAbsolute(
new Address(
$this->content->page->navbar->request->gtk->get_text()
)
);
}
return $address->get();
}
}

View file

@ -0,0 +1,105 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\Base;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\Go;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\History;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\Request;
class Navbar
{
public \GtkBox $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page $page;
// Requirements
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\Base $base;
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\Go $go;
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\History $history;
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\Request $request;
// Defaults
private int $_margin = 8;
public function __construct(
\Yggverse\Yoda\Entity\Browser\Container\Tab\Page $page
) {
// Init dependencies
$this->page = $page;
// Init navbar
$this->gtk = new \GtkBox(
\GtkOrientation::HORIZONTAL
);
$this->gtk->set_margin_top(
$this->_margin
);
$this->gtk->set_margin_bottom(
$this->_margin
);
$this->gtk->set_margin_start(
$this->_margin
);
$this->gtk->set_margin_end(
$this->_margin
);
$this->gtk->set_spacing(
$this->_margin
);
// Append base button
$this->base = new Base(
$this
);
$this->gtk->add(
$this->base->gtk
);
// Append history buttons group
$this->history = new History(
$this
);
$this->gtk->add(
$this->history->gtk
);
// Append request entry, fill empty space
$this->request = new Request(
$this
);
$this->gtk->pack_start(
$this->request->gtk,
true,
true,
0
);
// Append go button
$this->go = new Go(
$this
);
$this->gtk->add(
$this->go->gtk
);
}
public function refresh()
{
$this->base->refresh();
$this->go->refresh();
$this->history->refresh();
}
}

View file

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar;
class Base extends \Yggverse\Yoda\Abstract\Entity\Browser\Container\Tab\Page\Navbar\Button
{
protected string $_label = 'Base';
protected function _onCLick(
\GtkButton $entity
): void
{
$address = new \Yggverse\Net\Address(
trim(
strval(
$this->navbar->request->gtk->get_text()
)
)
);
if ($address->getHost())
{
$this->navbar->request->setValue(
$address->get( // build base
true,
true,
true,
true,
true,
false,
false,
false
)
);
$this->navbar->address->update();
}
$this->refresh();
}
public function refresh(): void
{
$address = new \Yggverse\Net\Address(
rtrim(
$this->navbar->request->gtk->get_text(),
'/'
)
);
$this->gtk->set_sensitive(
$address->getHost() && (
$address->getPath() || $address->getQuery()
)
);
}
}

View file

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar;
class Go extends \Yggverse\Yoda\Abstract\Entity\Browser\Container\Tab\Page\Navbar\Button
{
protected string $_label = 'Go';
protected function _onCLick(
\GtkButton $entity
): void
{
$this->navbar->page->update();
}
public function refresh(): void
{
$this->gtk->set_sensitive(
!empty(
trim(
$this->navbar->request->gtk->get_text()
)
)
);
}
}

View file

@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\History\Back;
use \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\History\Forward;
class History
{
public \GtkButtonBox $gtk;
// Dependencies
public \Yggverse\Yoda\Model\History $memory;
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar $navbar;
// Requirements
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\History\Back $back;
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\History\Forward $forward;
public function __construct(
\Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar $navbar
) {
$this->memory = new \Yggverse\Yoda\Model\History();
$this->navbar = $navbar;
$this->gtk = new \GtkButtonBox(
\GtkOrientation::HORIZONTAL
);
$this->gtk->set_layout(
\GtkButtonBoxStyle::EXPAND
);
$this->back = new Back(
$this->navbar
);
$this->gtk->add(
$this->back->gtk
);
$this->forward = new Forward(
$this->navbar
);
$this->gtk->add(
$this->forward->gtk
);
}
public function add(
string $value
): void
{
if (empty($value))
{
throw new \Exception;
}
if ($value != $this->memory->getCurrent())
{
$this->memory->add(
$value
);
}
$this->refresh();
}
public function refresh(): void
{
$this->back->refresh();
$this->forward->refresh();
}
}

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\History;
class Back extends \Yggverse\Yoda\Abstract\Entity\Browser\Container\Tab\Page\Navbar\Button
{
protected string $_label = 'Back';
protected function _onCLick(
\GtkButton $entity
): void
{
if ($this->navbar->history->memory->getBack())
{
$this->navbar->request->setValue(
$this->navbar->history->memory->goBack()
);
$this->navbar->page->update(
false
);
}
$this->navbar->history->refresh();
}
public function refresh(): void
{
$this->gtk->set_sensitive(
boolval(
$this->navbar->history->memory->getBack()
)
);
}
}

View file

@ -0,0 +1,37 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar\History;
class Forward extends \Yggverse\Yoda\Abstract\Entity\Browser\Container\Tab\Page\Navbar\Button
{
protected string $_label = 'Forward';
protected function _onCLick(
\GtkButton $entity
): void
{
if ($this->navbar->history->memory->getForward())
{
$this->navbar->request->setValue(
$this->navbar->history->memory->goForward()
);
$this->navbar->page->update(
false
);
}
$this->navbar->history->refresh();
}
public function refresh(): void
{
$this->gtk->set_sensitive(
boolval(
$this->navbar->history->memory->getForward()
)
);
}
}

View file

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page\Navbar;
class Request extends \Yggverse\Yoda\Abstract\Entity\Browser\Container\Tab\Page\Navbar\Entry
{
private string $_placeholder = 'URL or search term...';
protected function _onActivate(
\GtkEntry $entry
): void
{
$this->navbar->page->content->update();
}
protected function _onKeyRelease(
\GtkEntry $entry,
\GdkEvent $event
): void
{
$this->navbar->refresh();
}
}

View file

@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page;
class Statusbar
{
public \GtkLabel $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page $page;
// Defaults
private int $_margin = 8;
private string $_value = '';
public function __construct(
\Yggverse\Yoda\Entity\Browser\Container\Tab\Page $page
) {
// Init dependency
$this->page = $page;
// Init container
$this->gtk = new \GtkLabel;
$this->gtk->set_use_markup(
true
);
$this->gtk->set_selectable(
true
);
$this->gtk->set_line_wrap(
true
);
$this->gtk->set_xalign(
0
);
$this->gtk->set_yalign(
0
);
$this->gtk->set_margin_top(
$this->_margin
);
$this->gtk->set_margin_bottom(
$this->_margin
);
$this->gtk->set_margin_start(
$this->_margin
);
$this->gtk->set_margin_end(
$this->_margin
);
$this->gtk->set_markup(
$this->_value
);
}
public function setValue(
?string $value = null
): void
{
$this->gtk->set_markup(
is_null($value) ? $this->_value : trim(
$value
)
);
}
public function refresh()
{
// @TODO
}
}

View file

@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\Container\Tab\Page;
class Title
{
public \GtkLabel $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser\Container\Tab\Page $page;
// Defaults
private int $_ellipsize = 3;
private int $_length = 12;
private string $_value = 'New page';
public function __construct(
\Yggverse\Yoda\Entity\Browser\Container\Tab\Page $page,
) {
// Init dependencies
$this->page = $page;
// Init container
$this->gtk = new \GtkLabel(
$this->_value
);
$this->gtk->set_width_chars(
$this->_length
);
$this->gtk->set_ellipsize(
$this->_ellipsize
);
}
public function setValue(
?string $value = null
): void
{
$this->gtk->set_text(
is_null($value) ? $this->_value : trim(
$value
)
);
}
}

View file

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser;
class Header extends \Yggverse\Yoda\Abstract\Entity\HeaderBar
{
protected string $_title = 'Yoda';
}

View file

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser;
use \Yggverse\Yoda\Entity\Browser\History\Header;
use \Yggverse\Yoda\Entity\Browser\History\Container;
class History
{
public \GtkWindow $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser $browser;
// Requirements
public \Yggverse\Yoda\Entity\Browser\History\Header $header;
public \Yggverse\Yoda\Entity\Browser\History\Container $container;
// Defaults
private int $_width = 640;
private int $_height = 480;
private bool $_maximize = false;
public function __construct(
\Yggverse\Yoda\Entity\Browser $browser
) {
// Init dependencies
$this->browser = $browser;
// Init window
$this->gtk = new \GtkWindow;
$this->gtk->set_size_request(
$this->_width,
$this->_height
);
if ($this->_maximize)
{
$this->gtk->maximize();
}
// Init header
$this->header = new Header(
$this
);
$this->gtk->set_titlebar(
$this->header->gtk
);
// Init container
$this->container = new Container(
$this
);
$this->gtk->add(
$this->container->gtk
);
}
}

View file

@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\History;
use \Yggverse\Yoda\Entity\Browser\History\Container\Navbar;
use \Yggverse\Yoda\Entity\Browser\History\Container\Content;
class Container
{
public \GtkBox $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser\History $history;
// Requirements
public \Yggverse\Yoda\Entity\Browser\History\Container\Navbar $navbar;
public \Yggverse\Yoda\Entity\Browser\History\Container\Content $content;
public function __construct(
\Yggverse\Yoda\Entity\Browser\History $history
) {
// Init dependency
$this->history = $history;
// Init container
$this->gtk = new \GtkBox(
\GtkOrientation::VERTICAL
);
// Init navbar
$this->navbar = new Navbar(
$this
);
$this->gtk->add(
$this->navbar->gtk
);
// Init content
$this->content = new Content(
$this
);
$this->gtk->pack_start(
$this->content->gtk,
true,
true,
0
);
}
public function refresh()
{
$this->navbar->refresh();
$this->content->refresh();
}
}

View file

@ -0,0 +1,230 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\History\Container;
class Content
{
public \GtkScrolledWindow $gtk;
// Requirements @TODO entity
public \GtkTreeView $treeview;
public \GtkListStore $list;
// Dependencies
public \Yggverse\Yoda\Entity\Browser\History\Container $container;
// Defaults
private string $_time = 'Time';
private string $_title = 'Title';
private string $_url = 'URL';
private string $_format = 'c';
private int $_margin = 8;
public function __construct(
\Yggverse\Yoda\Entity\Browser\History\Container $container
) {
// Init dependency
$this->container = $container;
// Init container
$this->gtk = new \GtkScrolledWindow;
// Init records listing
$this->treeview = new \GtkTreeView;
$this->treeview->set_margin_start(
$this->_margin
);
$this->treeview->set_margin_end(
$this->_margin
);
$this->treeview->append_column(
new \GtkTreeViewColumn(
$this->_time,
new \GtkCellRendererText(),
'text',
1
)
);
$this->treeview->append_column(
new \GtkTreeViewColumn(
$this->_url,
new \GtkCellRendererText(),
'text',
2
)
);
$this->treeview->append_column(
new \GtkTreeViewColumn(
$this->_title,
new \GtkCellRendererText(),
'text',
3
)
);
$this->list = new \GtkListStore(
\GObject::TYPE_INT,
\GObject::TYPE_STRING,
\GObject::TYPE_STRING,
\GObject::TYPE_STRING
);
$this->treeview->set_model(
$this->list
);
$this->gtk->add(
$this->treeview
);
// Connect events
$this->treeview->connect(
'row-activated',
function(
\GtkTreeView $treeview
) {
$this->container->history->browser->container->tab->append(
$this->getSelectedUrl()
);
}
);
$this->treeview->connect(
'cursor-changed',
function(
\GtkTreeView $treeview
) {
$this->container->navbar->open->gtk->set_sensitive(
boolval(
$this->getSelectedId()
)
);
$this->container->navbar->delete->gtk->set_sensitive(
boolval(
$this->getSelectedId()
)
);
}
);
// Make initial search
$this->search();
}
// Append new row
public function append(
int $id,
int $time,
string $url,
?string $title
): void
{
$this->list->append(
[
$id,
date(
$this->_format,
$time
),
$url,
strval(
$title
)
]
);
}
// Remove rows from list
public function clear(): void
{
$this->list->clear();
}
// Refresh rows using current navbar value
public function refresh(): void
{
$this->search(
$this->container->navbar->filter->gtk->get_text()
);
}
// Do records search in database
public function search(
string $filter = ''
): void
{
$this->clear();
if ($records = $this->container->history->browser->database->findHistory($filter))
{
foreach ($records as $record)
{
$this->append(
$record->id,
$record->time,
$record->url,
$record->title
);
}
}
else
{
$this->container->navbar->open->gtk->set_sensitive(
false
);
$this->container->navbar->delete->gtk->set_sensitive(
false
);
}
}
public function getSelectedId(): ?int
{
if ($id = $this->_getSelected(0))
{
return $id;
}
return null;
}
public function getSelectedUrl(): ?string
{
if ($url = $this->_getSelected(2))
{
return $url;
}
return null;
}
private function _getSelected(
int $column
): null|int|string
{
list(
$list,
$row
) = $this->treeview->get_selection()->get_selected();
if ($list && $row)
{
if ($value = $list->get_value($row, $column))
{
return $value;
}
}
return null;
}
}

View file

@ -0,0 +1,104 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\History\Container;
use \Yggverse\Yoda\Entity\Browser\History\Container\Navbar\Delete;
use \Yggverse\Yoda\Entity\Browser\History\Container\Navbar\Filter;
use \Yggverse\Yoda\Entity\Browser\History\Container\Navbar\Open;
use \Yggverse\Yoda\Entity\Browser\History\Container\Navbar\Search;
class Navbar
{
public \GtkBox $gtk;
// Dependencies
public \Yggverse\Yoda\Entity\Browser\History\Container $container;
// Requirements
public \Yggverse\Yoda\Entity\Browser\History\Container\Navbar\Delete $delete;
public \Yggverse\Yoda\Entity\Browser\History\Container\Navbar\Filter $filter;
public \Yggverse\Yoda\Entity\Browser\History\Container\Navbar\Open $open;
public \Yggverse\Yoda\Entity\Browser\History\Container\Navbar\Search $search;
// Defaults
private int $_margin = 8;
public function __construct(
\Yggverse\Yoda\Entity\Browser\History\Container $container
) {
// Init dependency
$this->container = $container;
// Init container
$this->gtk = new \GtkBox(
\GtkOrientation::HORIZONTAL
);
$this->gtk->set_margin_top(
$this->_margin
);
$this->gtk->set_margin_bottom(
$this->_margin
);
$this->gtk->set_margin_start(
$this->_margin
);
$this->gtk->set_margin_end(
$this->_margin
);
$this->gtk->set_spacing(
$this->_margin
);
// Init open button
$this->open = new Open(
$this
);
$this->gtk->add(
$this->open->gtk
);
// Init delete button
$this->delete = new Delete(
$this
);
$this->gtk->add(
$this->delete->gtk
);
// Init filter entry
$this->filter = new Filter(
$this
);
$this->gtk->pack_start(
$this->filter->gtk,
true,
true,
0
);
// Init search button
$this->search = new Search(
$this
);
$this->gtk->add(
$this->search->gtk
);
}
public function refresh(): void
{
$this->delete->refresh();
$this->open->refresh();
}
}

View file

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\History\Container\Navbar;
class Delete extends \Yggverse\Yoda\Abstract\Entity\Browser\History\Container\Navbar\Button
{
protected string $_label = 'Delete';
protected function _onCLick(
\GtkButton $entity
): void
{
if ($id = $this->navbar->container->content->getSelectedId())
{
$this->navbar->container->history->browser->database->deleteHistory(
$id
);
}
$this->navbar->container->refresh();
}
public function refresh(): void
{
$this->gtk->set_sensitive(
boolval(
$this->navbar->container->content->getSelectedId()
)
);
}
}

View file

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\History\Container\Navbar;
class Filter extends \Yggverse\Yoda\Abstract\Entity\Browser\History\Container\Navbar\Entry
{
private string $_placeholder = 'Search in history...';
protected function _onActivate(
\GtkEntry $entry
): void
{
$this->navbar->container->content->search(
$entry->get_text()
);
}
protected function _onKeyRelease(
\GtkEntry $entry,
\GdkEvent $event
): void
{
$this->navbar->container->content->search(
$entry->get_text()
);
}
}

View file

@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\History\Container\Navbar;
class Open extends \Yggverse\Yoda\Abstract\Entity\Browser\History\Container\Navbar\Button
{
protected string $_label = 'Open';
protected function _onCLick(
\GtkButton $entity
): void
{
$this->navbar->container->history->browser->container->tab->append(
$this->navbar->container->content->getSelectedUrl()
);
}
public function refresh(): void
{
$this->gtk->set_sensitive(
boolval(
$this->navbar->container->content->getSelectedId()
)
);
}
}

View file

@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\History\Container\Navbar;
class Search extends \Yggverse\Yoda\Abstract\Entity\Browser\History\Container\Navbar\Button
{
protected bool $_sensitive = true;
protected string $_label = 'Search';
protected function _onCLick(
\GtkButton $entity
): void
{
$this->navbar->container->content->search(
$this->navbar->filter->gtk->get_text()
);
}
}

View file

@ -0,0 +1,10 @@
<?php
declare(strict_types=1);
namespace Yggverse\Yoda\Entity\Browser\History;
class Header extends \Yggverse\Yoda\Abstract\Entity\HeaderBar
{
protected string $_title = 'History - Yoda';
}