diff --git a/src/ant/ant/antService.cc b/src/ant/ant/antService.cc index 8477b6231..e380df795 100644 --- a/src/ant/ant/antService.cc +++ b/src/ant/ant/antService.cc @@ -1382,9 +1382,6 @@ dragging_what (const ant::Object *robj, const db::DBox &search_dbox, ant::Servic bool Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::angle_constraint_type /*ac*/) { - // cancel any pending move or drag operations, reset mp_active_ruler - ui ()->drag_cancel (); // KLUDGE: every service does this to the same service manager - clear_transient_selection (); // choose move mode diff --git a/src/db/db/dbLayoutQuery.cc b/src/db/db/dbLayoutQuery.cc index ce77f784f..37150d420 100644 --- a/src/db/db/dbLayoutQuery.cc +++ b/src/db/db/dbLayoutQuery.cc @@ -1938,9 +1938,11 @@ struct SelectFilterPropertyIDs SelectFilterPropertyIDs (LayoutQuery *q) { data = q->register_property ("data", LQ_variant); + expressions = q->register_property ("expressions", LQ_variant); } unsigned int data; // data -> An array of the selected values + unsigned int expressions; // data -> An array with the expressions }; class DB_PUBLIC SelectFilterReportingState @@ -2037,7 +2039,16 @@ class DB_PUBLIC SelectFilterState } } - virtual void reset (FilterStateBase *previous) + void get_expressions (tl::Variant &v) + { + std::vector vd; + v = tl::Variant (vd.begin (), vd.end ()); + for (std::vector::const_iterator e = m_expressions.begin (); e != m_expressions.end (); ++e) { + v.push (e->text ()); + } + } + + virtual void reset (FilterStateBase *previous) { if (m_has_sorting) { @@ -2082,6 +2093,9 @@ class DB_PUBLIC SelectFilterState if (id == m_pids.data) { get_data (v); return true; + } else if (id == m_pids.expressions) { + get_expressions (v); + return true; } else if (m_in_data_eval) { return FilterStateBase::get_property (id, v); } else { diff --git a/src/db/db/dbLayoutToNetlistReader.cc b/src/db/db/dbLayoutToNetlistReader.cc index 127cbc4b7..706999790 100644 --- a/src/db/db/dbLayoutToNetlistReader.cc +++ b/src/db/db/dbLayoutToNetlistReader.cc @@ -60,7 +60,7 @@ typedef l2n_std_format::keys skeys; typedef l2n_std_format::keys lkeys; LayoutToNetlistStandardReader::LayoutToNetlistStandardReader (tl::InputStream &stream) - : m_stream (stream), m_path (stream.absolute_path ()), m_dbu (0.0), + : m_stream (stream), m_path (stream.absolute_file_path ()), m_dbu (0.0), m_progress (tl::to_string (tr ("Reading L2N database")), 1000) { m_progress.set_format (tl::to_string (tr ("%.0fk lines"))); diff --git a/src/db/db/dbNetlistSpiceReader.cc b/src/db/db/dbNetlistSpiceReader.cc index 7e8563aa8..16684a39b 100644 --- a/src/db/db/dbNetlistSpiceReader.cc +++ b/src/db/db/dbNetlistSpiceReader.cc @@ -183,7 +183,7 @@ SpiceReaderStream::line_number () const std::string SpiceReaderStream::source () const { - return mp_stream->absolute_path (); + return mp_stream->absolute_file_path (); } bool @@ -495,7 +495,7 @@ SpiceCircuitDict::read (tl::InputStream &stream) m_global_net_names.clear (); m_global_nets.clear (); - m_file_id = file_id (stream.absolute_path ()); + m_file_id = file_id (stream.absolute_file_path ()); while (! at_end ()) { read_card (); diff --git a/src/db/db/dbTechnology.cc b/src/db/db/dbTechnology.cc index 84bd4118e..d28d00739 100644 --- a/src/db/db/dbTechnology.cc +++ b/src/db/db/dbTechnology.cc @@ -479,10 +479,10 @@ std::string Technology::correct_path (const std::string &fp) const { std::string bp = base_path (); - if (bp.empty ()) { + if (bp.empty () || ! tl::InputStream::is_file_path (fp) || ! tl::InputStream::is_file_path (bp)) { return fp; } else { - return tl::relative_path (bp, fp); + return tl::relative_path (tl::InputStream::as_file_path (bp), tl::InputStream::as_file_path (fp)); } } @@ -494,7 +494,11 @@ Technology::load (const std::string &fn) xml_struct.parse (source, *this); // use the tech file's path as the default base path - set_default_base_path (tl::absolute_path (fn)); + if (tl::InputStream::is_file_path (fn)) { + set_default_base_path (tl::absolute_path (fn)); + } else { + set_default_base_path (std::string ()); + } set_tech_file_path (fn); } @@ -515,10 +519,10 @@ Technology::build_effective_path (const std::string &p) const return p; } - if (tl::is_absolute (p)) { + if (tl::InputStream::is_absolute (p)) { return p; } else { - return tl::combine_path (bp, p); + return tl::InputStream::combine (bp, p); } } diff --git a/src/edt/edt/edtPartialService.cc b/src/edt/edt/edtPartialService.cc index f6973af78..b57e5a3cd 100644 --- a/src/edt/edt/edtPartialService.cc +++ b/src/edt/edt/edtPartialService.cc @@ -1811,7 +1811,7 @@ PartialService::mouse_move_event (const db::DPoint &p, unsigned int buttons, boo if (snap_details.object_snap == lay::PointSnapToObjectResult::NoObject) { m_current = m_start + snap_move (p - m_start); } else { - m_current = snap_details.snapped_point; + m_current = m_start + snap_move (snap_details.snapped_point - m_start); mouse_cursor_from_snap_details (snap_details); } diff --git a/src/img/img/imgService.cc b/src/img/img/imgService.cc index 249e1a0f9..3c50e0531 100644 --- a/src/img/img/imgService.cc +++ b/src/img/img/imgService.cc @@ -588,9 +588,6 @@ Service::mouse_move_event (const db::DPoint & /*p*/, unsigned int /*buttons*/, b bool Service::begin_move (lay::Editable::MoveMode mode, const db::DPoint &p, lay::angle_constraint_type /*ac*/) { - // cancel any pending move or drag operations - widget ()->drag_cancel (); // KLUDGE: every service does this to the same service manager - // compute search box double l = catch_distance (); db::DBox search_dbox = db::DBox (p, p).enlarged (db::DVector (l, l)); diff --git a/src/klayout.pro b/src/klayout.pro index e95f96610..2bb0bf555 100644 --- a/src/klayout.pro +++ b/src/klayout.pro @@ -25,7 +25,6 @@ equals(HAVE_RUBY, "1") { !equals(HAVE_QT, "0") { - # TODO: make buddies able to build without Qt SUBDIRS += \ klayout_main \ lay \ diff --git a/src/lay/lay/TechBaseEditorPage.ui b/src/lay/lay/TechBaseEditorPage.ui index 17d89749d..5d2cf64f3 100644 --- a/src/lay/lay/TechBaseEditorPage.ui +++ b/src/lay/lay/TechBaseEditorPage.ui @@ -14,74 +14,18 @@ Form - - - - Qt::Horizontal - - - - - - - Qt::Horizontal - - - - - - - - - - Database -unit - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - - - + + Qt::Horizontal - - - - - - ... - - - - - - - QAbstractItemView::NoSelection - - - - - - - (Use the rename button to change this) - - - - - - - - - - The base path is used to locate auxiliary files if those are specified with a relative path. If none is specified, the default path is used. The default path is the one from which a technology was imported. - - - true + + + 40 + 20 + - + @@ -124,122 +68,13 @@ unit - - - - - - - (Used for creating tech groups) - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 5 - - - - - - - - Technology -specific -libraries - - - - - - - Grids - - - - - - - Base path - - - - - + + - Description - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - QFrame::NoFrame - - - QFrame::Raised + Group - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - µm (g1,g2,...) - - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -284,10 +119,61 @@ libraries - - + + + + + + + false + + + + + + + The default database unit is used as database unit for freshly created layouts + + + true + + + + + + + Name + + + + + + + QFrame::NoFrame + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + - Automatically add other layers + µm (g1,g2,...) @@ -305,34 +191,76 @@ properties - - + + - Name + The base path is used to locate auxiliary files if those are specified with a relative path. If none is specified, the default path is used. The default path is the one from which a technology was imported. + + + true - - - - false + + + + These grids are available for selection from the "View" menu and will override the general ones. You can declare one grid as a strong default to enforce an editing grid from this list. To do so, add an exclamation mark to the grid - e.g. "0.01!,0.02,0.05". + + + true - - + + - Group + Technology +specific +libraries - - - - The default database unit is used as database unit for freshly created layouts + + + + Qt::Horizontal - - true + + + + + + Qt::Horizontal + + + + + + + Qt::Horizontal + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 5 + + + + + + + + Base path @@ -352,13 +280,85 @@ properties - - + + - These grids are available for selection from the "View" menu and will override the general ones. You can declare one grid as a strong default to enforce an editing grid from this list. To do so, add an exclamation mark to the grid - e.g. "0.01!,0.02,0.05". + Database +unit - - true + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + QAbstractItemView::NoSelection + + + + + + + (Used for creating tech groups) + + + + + + + ... + + + + + + + Grids + + + + + + + Description + + + + + + + + + + (Use the rename button to change this) + + + + + + + + + + + + + Initialize other layers with default properties diff --git a/src/lay/lay/layMainWindow.cc b/src/lay/lay/layMainWindow.cc index a3f667791..f7444453c 100644 --- a/src/lay/lay/layMainWindow.cc +++ b/src/lay/lay/layMainWindow.cc @@ -2981,7 +2981,7 @@ void MainWindow::add_mru (const std::string &fn_rel, const std::string &tech) { std::vector > new_mru; - std::string fn (tl::InputStream::absolute_path (fn_rel)); + std::string fn (tl::InputStream::absolute_file_path (fn_rel)); for (auto mru = m_mru.begin (); mru != m_mru.end (); ++mru) { if (mru->first != fn /* delete non-existing files: && tl::is_readable (mru->first) */) { @@ -3025,7 +3025,7 @@ MainWindow::add_to_other_mru (const std::string &fn_rel, const std::string &cfg) } std::vector new_mru; - std::string fn (tl::InputStream::absolute_path (fn_rel)); + std::string fn (tl::InputStream::absolute_file_path (fn_rel)); for (auto mru = mru_ptr->begin (); mru != mru_ptr->end (); ++mru) { if (*mru != fn /* delete non-existing files: && tl::is_readable (*mru) */) { diff --git a/src/lay/lay/laySearchReplaceDialog.cc b/src/lay/lay/laySearchReplaceDialog.cc index 896bdb616..c05996b33 100644 --- a/src/lay/lay/laySearchReplaceDialog.cc +++ b/src/lay/lay/laySearchReplaceDialog.cc @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include @@ -76,6 +78,15 @@ SearchReplaceResults::clear () m_has_more = false; } +void +SearchReplaceResults::set_data_column_headers (const tl::Variant &v) +{ + m_data_column_headers = v; + if (v.is_list ()) { + m_data_columns = std::max (v.get_list ().size (), m_data_columns); + } +} + void SearchReplaceResults::push_back (const tl::Variant &v) { @@ -166,7 +177,13 @@ SearchReplaceResults::headerData (int section, Qt::Orientation /*orientation*/, { if (role == Qt::DisplayRole) { if (! m_data_result.empty ()) { - if (section == 0) { + if (m_data_column_headers.is_list ()) { + if (section < int (m_data_column_headers.get_list ().size ())) { + return QVariant (m_data_column_headers.get_list () [section].to_string ()); + } else { + return QVariant (QString ()); + } + } else if (section == 0) { return QVariant (QObject::tr ("Value")); } else { return QVariant (QString ()); @@ -522,11 +539,37 @@ SearchReplaceResults::select_items (lay::LayoutViewBase *view, int cv_index, con edt::set_object_selection (view, sel); } -void +void +SearchReplaceResults::export_csv_to_clipboard (const std::set *rows) +{ + tl::OutputMemoryStream buffer; + + { + tl::OutputStream os (buffer, true /* as text */); + export_csv (os, rows); + } + +#if QT_VERSION >= 0x050000 + QClipboard *clipboard = QGuiApplication::clipboard(); +#else + QClipboard *clipboard = QApplication::clipboard(); +#endif + QMimeData *data = new QMimeData (); + data->setData (QString::fromUtf8 ("text/csv"), QByteArray (buffer.data (), buffer.size ())); + data->setText (QString::fromUtf8 (buffer.data (), buffer.size ())); + clipboard->setMimeData (data); +} + +void SearchReplaceResults::export_csv (const std::string &file, const std::set *rows) { - std::ofstream output (file.c_str ()); + tl::OutputStream os (file, tl::OutputStream::OM_Auto, true /* as text */); + export_csv (os, rows); +} +void +SearchReplaceResults::export_csv (tl::OutputStream &os, const std::set *rows) +{ QModelIndex parent; size_t n_columns = columnCount (parent); @@ -534,11 +577,11 @@ SearchReplaceResults::export_csv (const std::string &file, const std::set * for (size_t c = 0; c < n_columns; ++c) { if (c) { - output << ","; + os << ","; } - output << escape_csv (tl::to_string (headerData (int (c), Qt::Horizontal, Qt::DisplayRole).toString ())); + os << escape_csv (tl::to_string (headerData (int (c), Qt::Horizontal, Qt::DisplayRole).toString ())); } - output << std::endl; + os << "\n"; for (size_t r = 0; r < n_rows; ++r) { @@ -546,13 +589,13 @@ SearchReplaceResults::export_csv (const std::string &file, const std::set * for (size_t c = 0; c < n_columns; ++c) { if (c) { - output << ","; + os << ","; } // TODO: optimize - output << escape_csv (tl::to_string (data (index (int (r), int (c), parent), Qt::DisplayRole).toString ())); + os << escape_csv (tl::to_string (data (index (int (r), int (c), parent), Qt::DisplayRole).toString ())); } - output << std::endl; + os << "\n"; } @@ -843,6 +886,7 @@ SearchReplaceDialog::SearchReplaceDialog (lay::Dispatcher *root, LayoutViewBase connect (results->header (), SIGNAL (sectionCountChanged (int, int)), this, SLOT (header_columns_changed (int, int))); QMenu *menu = new QMenu (this); + menu->addAction (QObject::tr ("Copy to clipboard"), this, SLOT (export_csv_to_clipboard ())); menu->addAction (QObject::tr ("To CSV file"), this, SLOT (export_csv ())); menu->addAction (QObject::tr ("To report database"), this, SLOT (export_rdb ())); menu->addAction (QObject::tr ("To layout"), this, SLOT (export_layout ())); @@ -851,6 +895,10 @@ SearchReplaceDialog::SearchReplaceDialog (lay::Dispatcher *root, LayoutViewBase QAction *action; + action = new QAction (QObject::tr ("Copy to clipboard"), results); + connect (action, SIGNAL (triggered ()), this, SLOT (sel_export_csv_to_clipboard ())); + results->addAction (action); + action = new QAction (QObject::tr ("Export to CSV file"), results); connect (action, SIGNAL (triggered ()), this, SLOT (sel_export_csv ())); results->addAction (action); @@ -1165,6 +1213,54 @@ BEGIN_PROTECTED END_PROTECTED } +void +SearchReplaceDialog::sel_export_csv_to_clipboard () +{ +BEGIN_PROTECTED + + std::set rows; + QModelIndexList sel = results->selectionModel ()->selectedRows (0); + for (auto s = sel.begin (); s != sel.end (); ++s) { + rows.insert (s->row ()); + } + + m_model.export_csv_to_clipboard (&rows); + +END_PROTECTED +} + +void +SearchReplaceDialog::export_csv_to_clipboard () +{ +BEGIN_PROTECTED + + int cv_index = m_last_query_cv_index; + const lay::CellView &cv = mp_view->cellview (cv_index); + if (! cv.is_valid ()) { + return; + } + + db::LayoutQuery lq (m_last_query); + + tl::AbsoluteProgress progress (tl::to_string (QObject::tr ("Running query"))); + progress.set_unit (100000); + progress.set_format ("Processing .."); + + db::LayoutQueryIterator iq (lq, &cv->layout (), 0, &progress); + + if (tl::verbosity () >= 10) { + tl::log << tl::to_string (QObject::tr ("Running query: ")) << m_last_query; + } + + SearchReplaceResults model; + model.begin_changes (& cv->layout ()); + query_to_model (model, lq, iq, std::numeric_limits::max (), true); + model.end_changes (); + model.export_csv_to_clipboard (); + +END_PROTECTED +} + void SearchReplaceDialog::sel_export_rdb () { @@ -1697,6 +1793,7 @@ SearchReplaceDialog::query_to_model (SearchReplaceResults &model, const db::Layo bool res = false; int data_prop_id = lq.has_property ("data") ? int (lq.property_by_name ("data")) : -1; + int expressions_prop_id = lq.has_property ("expressions") ? int (lq.property_by_name ("expressions")) : -1; int shape_prop_id = lq.has_property ("shape") ? int (lq.property_by_name ("shape")) : -1; int layer_index_prop_id = lq.has_property ("layer_index") ? int (lq.property_by_name ("layer_index")) : -1; int instance_prop_id = lq.has_property ("inst") ? int (lq.property_by_name ("inst")) : -1; @@ -1707,6 +1804,11 @@ SearchReplaceDialog::query_to_model (SearchReplaceResults &model, const db::Layo int parent_cell_index_prop_id = lq.has_property ("parent_cell_index") ? int (lq.property_by_name ("parent_cell_index")) : -1; int initial_cell_index_prop_id = lq.has_property ("initial_cell_index") ? int (lq.property_by_name ("initial_cell_index")) : -1; + tl::Variant ve; + if (expressions_prop_id >= 0 && iq.get (expressions_prop_id, ve)) { + model.set_data_column_headers (ve); + } + while (! iq.at_end ()) { if (++n > max_item_count) { diff --git a/src/lay/lay/laySearchReplaceDialog.h b/src/lay/lay/laySearchReplaceDialog.h index 48f7ddd88..ad572e184 100644 --- a/src/lay/lay/laySearchReplaceDialog.h +++ b/src/lay/lay/laySearchReplaceDialog.h @@ -98,6 +98,7 @@ Q_OBJECT SearchReplaceResults (); void clear (); + void set_data_column_headers (const tl::Variant &v); void push_back (const tl::Variant &v); void push_back (const QueryShapeResult &v); void push_back (const QueryInstResult &v); @@ -157,6 +158,8 @@ Q_OBJECT void has_more (bool hm); void export_csv (const std::string &file, const std::set *rows = 0); + void export_csv_to_clipboard (const std::set *rows = 0); + void export_csv (tl::OutputStream &os, const std::set *rows = 0); void export_layout (db::Layout &layout, const std::set *rows = 0); void export_rdb (rdb::Database &rdb, double dbu, const std::set *rows = 0); void select_items (LayoutViewBase *view, int cv_index, const std::set *rows = 0); @@ -167,6 +170,7 @@ Q_OBJECT std::vector m_inst_result; std::vector m_cell_result; size_t m_data_columns; + tl::Variant m_data_column_headers; mutable int m_last_column_count; std::map m_cellname_map; std::map m_lp_map; @@ -239,10 +243,12 @@ private slots: void cancel_exec (); void select_items (); void export_csv (); + void export_csv_to_clipboard (); void export_rdb (); void export_layout (); void sel_select_items (); void sel_export_csv (); + void sel_export_csv_to_clipboard (); void sel_export_rdb (); void sel_export_layout (); diff --git a/src/lay/lay/laySession.cc b/src/lay/lay/laySession.cc index bed76e234..72494b1d3 100644 --- a/src/lay/lay/laySession.cc +++ b/src/lay/lay/laySession.cc @@ -66,7 +66,7 @@ Session::fetch (const lay::MainWindow &mw) if (lh) { m_layouts.push_back (SessionLayoutDescriptor ()); m_layouts.back ().name = *l; - m_layouts.back ().file_path = tl::InputStream::absolute_path (lh->filename ()); + m_layouts.back ().file_path = tl::InputStream::absolute_file_path (lh->filename ()); m_layouts.back ().load_options = lh->load_options (); m_layouts.back ().save_options = lh->save_options (); m_layouts.back ().save_options_valid = lh->save_options_valid (); @@ -89,7 +89,7 @@ Session::fetch (const lay::MainWindow &mw) const rdb::Database *rdb = view->get_rdb (j); if (rdb && ! rdb->filename ().empty ()) { - view_desc.rdb_filenames.push_back (tl::InputStream::absolute_path (rdb->filename ())); + view_desc.rdb_filenames.push_back (tl::InputStream::absolute_file_path (rdb->filename ())); } } @@ -98,7 +98,7 @@ Session::fetch (const lay::MainWindow &mw) const db::LayoutToNetlist *l2ndb = view->get_l2ndb (j); if (l2ndb && ! l2ndb->filename ().empty ()) { - view_desc.l2ndb_filenames.push_back (tl::InputStream::absolute_path (l2ndb->filename ())); + view_desc.l2ndb_filenames.push_back (tl::InputStream::absolute_file_path (l2ndb->filename ())); } } diff --git a/src/lay/lay/layTechSetupDialog.cc b/src/lay/lay/layTechSetupDialog.cc index b690ce8c1..e3773fd1c 100644 --- a/src/lay/lay/layTechSetupDialog.cc +++ b/src/lay/lay/layTechSetupDialog.cc @@ -173,11 +173,10 @@ TechBaseEditorPage::commit () if (! mp_ui->lyp_grp->isChecked ()) { tech ()->set_layer_properties_file (std::string ()); - tech ()->set_add_other_layers (true); } else { tech ()->set_layer_properties_file (tl::to_string (mp_ui->lyp_le->text ())); - tech ()->set_add_other_layers (mp_ui->add_other_layers_cbx->isChecked ()); } + tech ()->set_add_other_layers (mp_ui->add_other_layers_cbx->isChecked ()); } void diff --git a/src/laybasic/laybasic/layAbstractMenu.cc b/src/laybasic/laybasic/layAbstractMenu.cc index bac75edbd..44de01ab0 100644 --- a/src/laybasic/laybasic/layAbstractMenu.cc +++ b/src/laybasic/laybasic/layAbstractMenu.cc @@ -76,15 +76,16 @@ std::vector > unpack_key_binding (const std::string &packed) { tl::Extractor ex (packed.c_str ()); + ex.test(";"); // backward compatibiliy std::vector > key_bindings; while (! ex.at_end ()) { - ex.test(";"); key_bindings.push_back (std::make_pair (std::string (), std::string ())); ex.read_word_or_quoted (key_bindings.back ().first); ex.test(":"); ex.read_word_or_quoted (key_bindings.back ().second); + ex.test(";"); } return key_bindings; @@ -93,17 +94,26 @@ unpack_key_binding (const std::string &packed) std::string pack_key_binding (const std::vector > &unpacked) { - std::string packed; + std::string packed = "\n"; + bool first = true; - for (std::vector >::const_iterator p = unpacked.begin (); p != unpacked.end (); ++p) { - if (! packed.empty ()) { - packed += ";"; + // for easier editing we separate the entries into non-empty and empty ones and put each of them on a new line + for (int pass = 0; pass < 2; ++pass) { + for (std::vector >::const_iterator p = unpacked.begin (); p != unpacked.end (); ++p) { + if ((pass == 0) == p->second.empty ()) { + continue; + } + if (! first) { + packed += ";\n"; + } + first = false; + packed += tl::to_word_or_quoted_string (p->first); + packed += ":"; + packed += tl::to_word_or_quoted_string (p->second); } - packed += tl::to_word_or_quoted_string (p->first); - packed += ":"; - packed += tl::to_word_or_quoted_string (p->second); } + packed += "\n"; return packed; } @@ -111,15 +121,16 @@ std::vector > unpack_menu_items_hidden (const std::string &packed) { tl::Extractor ex (packed.c_str ()); + ex.test(";"); // backward compatibiliy std::vector > hidden; while (! ex.at_end ()) { - ex.test(";"); hidden.push_back (std::make_pair (std::string (), false)); ex.read_word_or_quoted (hidden.back ().first); ex.test(":"); ex.read (hidden.back ().second); + ex.test(";"); } return hidden; @@ -128,17 +139,26 @@ unpack_menu_items_hidden (const std::string &packed) std::string pack_menu_items_hidden (const std::vector > &unpacked) { - std::string packed; + std::string packed = "\n"; + bool first = true; - for (std::vector >::const_iterator p = unpacked.begin (); p != unpacked.end (); ++p) { - if (! packed.empty ()) { - packed += ";"; + // for easier editing we separate the entries into true and false ones and put each of them on a new line + for (int pass = 0; pass < 2; ++pass) { + for (std::vector >::const_iterator p = unpacked.begin (); p != unpacked.end (); ++p) { + if ((pass == 0) != p->second) { + continue; + } + if (! first) { + packed += ";\n"; + } + first = false; + packed += tl::to_word_or_quoted_string (p->first); + packed += ":"; + packed += tl::to_string (p->second); } - packed += tl::to_word_or_quoted_string (p->first); - packed += ":"; - packed += tl::to_string (p->second); } + packed += "\n"; return packed; } diff --git a/src/laybasic/laybasic/layEditable.cc b/src/laybasic/laybasic/layEditable.cc index 17cc210d3..98290689d 100644 --- a/src/laybasic/laybasic/layEditable.cc +++ b/src/laybasic/laybasic/layEditable.cc @@ -165,9 +165,9 @@ Editables::selection_catch_bbox () } void -Editables::transform (const db::DCplxTrans &t, db::Transaction *transaction) +Editables::transform (const db::DCplxTrans &t) { - std::unique_ptr trans_holder (transaction ? transaction : new db::Transaction (manager (), tl::to_string (tr ("Transform")))); + std::unique_ptr trans_holder (new db::Transaction (manager (), tl::to_string (tr ("Transform")))); if (has_selection ()) { @@ -639,15 +639,31 @@ Editables::edit_cancel () } } +void +Editables::edit_finish () +{ + clear_previous_selection (); + for (iterator e = begin (); e != end (); ++e) { + e->edit_finish (); + } +} + void Editables::cancel_edits () { - // cancel any edit operations for (iterator e = begin (); e != end (); ++e) { e->edit_cancel (); } } +void +Editables::finish_edits () +{ + for (iterator e = begin (); e != end (); ++e) { + e->edit_finish (); + } +} + void Editables::show_properties () { diff --git a/src/laybasic/laybasic/layEditable.h b/src/laybasic/laybasic/layEditable.h index 486223981..779677db8 100644 --- a/src/laybasic/laybasic/layEditable.h +++ b/src/laybasic/laybasic/layEditable.h @@ -332,6 +332,20 @@ class LAYBASIC_PUBLIC Editable // .. by default, nothing is implemented .. } + /** + * @brief Finishes any pending operations + * + * This event is sent whenever a pending operation such as + * a move operation should be finished. + * In contrast to "edit_cancel", this version is supposed + * not to rollback, for example if transactions are involved. + */ + virtual void edit_finish () + { + // by default maps to edit_cancel + edit_cancel (); + } + /** * @brief Indicates if any objects are selected */ @@ -457,14 +471,11 @@ class LAYBASIC_PUBLIC Editables db::DBox selection_bbox (); /** - * @brief transform the selection + * @brief Transforms the selection * * The transformation is given in micron units. - * - * If a transaction is given, the operation will be appended to this pending transaction. - * The Editables object takes ownership over the Transaction object. */ - void transform (const db::DCplxTrans &tr, db::Transaction *transaction = 0); + virtual void transform (const db::DCplxTrans &tr); /** * @brief Enable or disable a certain editable @@ -565,9 +576,18 @@ class LAYBASIC_PUBLIC Editables /** * @brief Cancel any pending operations + * + * This method calls "edit_cancel" on all services and resets selection tracking. */ void edit_cancel (); + /** + * @brief Finishes any pending operations + * + * This method calls "edit_finish" on all services and resets selection tracking. + */ + void edit_finish (); + /** * @brief Editable iterator: begin */ @@ -633,11 +653,21 @@ class LAYBASIC_PUBLIC Editables * @brief Cancel all edit operations * * This method can be overridden in order to implement special behaviour on cancel - * of edits (i.e. release the mouse). + * of edits (e.g. clean up markers). * Make sure, the base implementation is called as well. */ virtual void cancel_edits (); + /** + * @brief Finishes all edit operations + * + * This method can be overridden in order to implement special behaviour on finishing + * of edits (e.g. clean up markers). In contrast to "cancel_edits", this method + * is expected not to rollback any operations - i.e. undo transactions. + * Make sure, the base implementation is called as well. + */ + virtual void finish_edits (); + private: friend class Editable; diff --git a/src/laybasic/laybasic/layFinder.cc b/src/laybasic/laybasic/layFinder.cc index b874cff6c..0b3303d05 100644 --- a/src/laybasic/laybasic/layFinder.cc +++ b/src/laybasic/laybasic/layFinder.cc @@ -235,6 +235,8 @@ Finder::do_find (const db::Cell &cell, int level, const db::DCplxTrans &vp, cons m_path.push_back (db::InstElement (*inst, p)); + checkpoint (); + do_find (mp_layout->cell (cell_inst.object ().cell_index ()), level + 1, vp, @@ -772,6 +774,14 @@ InstFinder::find_internal (LayoutViewBase *view, unsigned int cv_index, const db return ! m_founds.empty (); } +void +InstFinder::checkpoint () +{ + if (--m_tries < 0) { + throw StopException (); + } +} + void InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const db::Box & /*scan_box*/, const db::DCplxTrans & /*vp*/, const db::ICplxTrans &t, int level) { @@ -854,17 +864,13 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d } else { - if (--m_tries < 0) { - throw StopException (); - } + checkpoint (); // look for instances to check here .. db::Cell::touching_iterator inst = cell.begin_touching (search_box); while (! inst.at_end ()) { - if (--m_tries < 0) { - throw StopException (); - } + checkpoint (); const db::CellInstArray &cell_inst = inst->cell_inst (); const db::Cell &inst_cell = layout ().cell (cell_inst.object ().cell_index ()); @@ -877,9 +883,7 @@ InstFinder::visit_cell (const db::Cell &cell, const db::Box &search_box, const d db::box_convert bc (layout ()); for (db::CellInstArray::iterator p = cell_inst.begin_touching (search_box, bc); ! p.at_end (); ++p) { - if (--m_tries < 0) { - throw StopException (); - } + checkpoint (); bool match = false; double d = std::numeric_limits::max (); diff --git a/src/laybasic/laybasic/layFinder.h b/src/laybasic/laybasic/layFinder.h index 0346f305b..b5edeaa71 100644 --- a/src/laybasic/laybasic/layFinder.h +++ b/src/laybasic/laybasic/layFinder.h @@ -189,6 +189,11 @@ class LAYBASIC_PUBLIC Finder */ void test_edge (const db::ICplxTrans &trans, const db::Edge &edge, double &distance, bool &match); + /** + * @brief Is called "frequently", so the finder can stop after a number of tries and not waste time + */ + virtual void checkpoint () = 0; + private: void do_find (const db::Cell &cell, int level, const db::DCplxTrans &vp, const db::ICplxTrans &t); @@ -282,7 +287,7 @@ class LAYBASIC_PUBLIC ShapeFinder m_tries = n; } - void checkpoint (); + virtual void checkpoint (); private: virtual void visit_cell (const db::Cell &cell, const db::Box &hit_box, const db::Box &scan_box, const db::DCplxTrans &vp, const db::ICplxTrans &t, int level); @@ -339,6 +344,8 @@ class LAYBASIC_PUBLIC InstFinder return m_founds.end (); } + virtual void checkpoint (); + private: virtual void visit_cell (const db::Cell &cell, const db::Box &hit_box, const db::Box &scan_box, const db::DCplxTrans &vp, const db::ICplxTrans &t, int level); diff --git a/src/laybasic/laybasic/layLayoutViewBase.cc b/src/laybasic/laybasic/layLayoutViewBase.cc index 4213ffe6d..a5341edb2 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.cc +++ b/src/laybasic/laybasic/layLayoutViewBase.cc @@ -2522,7 +2522,7 @@ LayoutViewBase::signal_apply_technology (lay::LayoutHandle *layout_handle) lyp_file = tech->eff_layer_properties_file (); } - if (! lyp_file.empty ()) { + if (! lyp_file.empty () || tech->add_other_layers ()) { // interpolate the layout properties file name tl::Eval expr; @@ -3983,12 +3983,44 @@ LayoutViewBase::redraw () mp_canvas->redraw_new (layers); } +void +LayoutViewBase::transform (const db::DCplxTrans &tr) +{ + // NOTE: we call "finish_edits" rather than "cancel_edits" because + // "move by" while "duplicate interactive" relies on keeping the + // pasted shapes from the previous transaction. So we must not roll back. + finish_edits (); + lay::Editables::transform (tr); +} + void LayoutViewBase::cancel_edits () { + // the move service takes a special role here as it manages the + // transaction for the collective move operation. + mp_move_service->cancel (); + // cancel all drag and pending edit operations such as move operations. mp_canvas->drag_cancel (); lay::Editables::cancel_edits (); + + // re-enable edit mode + enable_edits (true); +} + +void +LayoutViewBase::finish_edits () +{ + // the move service takes a special role here as it manages the + // transaction for the collective move operation. + mp_move_service->finish (); + + // cancel all drag operations + mp_canvas->drag_cancel (); + lay::Editables::finish_edits (); + + // re-enable edit mode + enable_edits (true); } void @@ -3996,8 +4028,6 @@ LayoutViewBase::cancel () { // cancel all drags and pending edit operations such as move operations. cancel_edits (); - // re-enable edit mode - enable_edits (true); // and clear the selection clear_selection (); } diff --git a/src/laybasic/laybasic/layLayoutViewBase.h b/src/laybasic/laybasic/layLayoutViewBase.h index 85257d42f..2682e8477 100644 --- a/src/laybasic/laybasic/layLayoutViewBase.h +++ b/src/laybasic/laybasic/layLayoutViewBase.h @@ -2666,6 +2666,13 @@ class LAYBASIC_PUBLIC LayoutViewBase : */ void cancel_edits (); + /** + * @brief Finishes all edit operations and maintains selection + * + * In contrast to "cancel_edits" there is no rollback of operations applied already. + */ + void finish_edits (); + /** * @brief Select all levels of hierarchy available */ @@ -2696,7 +2703,14 @@ class LAYBASIC_PUBLIC LayoutViewBase : */ void ensure_selection_visible (); - /** + /** + * @brief Transforms the selection + * + * The transformation is given in micron units. + */ + virtual void transform (const db::DCplxTrans &tr); + + /** * @brief Select a cell by index for a certain cell view * * This will be forwarded to select_cell or select_cell_fit depending diff --git a/src/laybasic/laybasic/layMove.cc b/src/laybasic/laybasic/layMove.cc index 4bc22e7d0..28266bfb9 100644 --- a/src/laybasic/laybasic/layMove.cc +++ b/src/laybasic/laybasic/layMove.cc @@ -302,6 +302,7 @@ MoveService::handle_click (const db::DPoint &p, unsigned int buttons, bool drag_ if (! m_dragging) { mp_transaction.reset (trans_holder.release ()); + ui ()->drag_cancel (); if (mp_editables->begin_move (p, ac_from_buttons (buttons))) { @@ -337,21 +338,31 @@ MoveService::handle_click (const db::DPoint &p, unsigned int buttons, bool drag_ } void -MoveService::drag_cancel () -{ +MoveService::drag_cancel () +{ m_shift = db::DPoint (); if (m_dragging) { - - mp_editables->edit_cancel (); ui ()->ungrab_mouse (this); - m_dragging = false; + } +} +void +MoveService::cancel () +{ + if (m_dragging) { if (mp_transaction.get ()) { mp_transaction->cancel (); } mp_transaction.reset (0); + } +} +void +MoveService::finish () +{ + if (m_dragging) { + mp_transaction.reset (0); } } diff --git a/src/laybasic/laybasic/layMove.h b/src/laybasic/laybasic/layMove.h index 8e3daf124..ea0bf2758 100644 --- a/src/laybasic/laybasic/layMove.h +++ b/src/laybasic/laybasic/layMove.h @@ -43,6 +43,8 @@ class LAYBASIC_PUBLIC MoveService : bool configure (const std::string &name, const std::string &value); bool begin_move (db::Transaction *transaction = 0, bool transient_selection = false); + void finish (); + void cancel (); private: virtual bool mouse_press_event (const db::DPoint &p, unsigned int buttons, bool prio); diff --git a/src/laybasic/laybasic/layViewObject.h b/src/laybasic/laybasic/layViewObject.h index 89698149e..994f7eca1 100644 --- a/src/laybasic/laybasic/layViewObject.h +++ b/src/laybasic/laybasic/layViewObject.h @@ -276,7 +276,7 @@ class LAYBASIC_PUBLIC ViewService virtual void set_colors (tl::Color /*background*/, tl::Color /*text*/) { } /** - * @brief This method is called when a drag operation should be cancelled + * @brief This method is called when a mouse tracking operation should be cancelled */ virtual void drag_cancel () { } diff --git a/src/layui/layui/NetlistBrowserPage.ui b/src/layui/layui/NetlistBrowserPage.ui index 57a2e16e0..ca8593857 100644 --- a/src/layui/layui/NetlistBrowserPage.ui +++ b/src/layui/layui/NetlistBrowserPage.ui @@ -89,8 +89,8 @@ ... - - :/back_16.png:/back_16.png + + :/back_16px.png:/back_16px.png true @@ -103,8 +103,8 @@ ... - - :/forward_16.png:/forward_16.png + + :/forward_16px.png:/forward_16px.png true diff --git a/src/layui/layui/layLayoutViewFunctions.cc b/src/layui/layui/layLayoutViewFunctions.cc index da5bdafad..20ec394b2 100644 --- a/src/layui/layui/layLayoutViewFunctions.cc +++ b/src/layui/layui/layLayoutViewFunctions.cc @@ -1199,8 +1199,7 @@ LayoutViewFunctions::do_cm_duplicate (bool interactive) try { bool transient_mode = ! view ()->has_selection (); view ()->copy_view_objects (); - view ()->clear_selection (); - view ()->cancel (); + view ()->cancel_edits (); if (interactive) { view ()->paste_interactive (transient_mode); } else { @@ -1217,8 +1216,7 @@ void LayoutViewFunctions::do_cm_paste (bool interactive) { if (! db::Clipboard::instance ().empty ()) { - view ()->cancel (); - view ()->clear_selection (); + view ()->cancel_edits (); if (interactive) { view ()->paste_interactive (); } else { @@ -1330,9 +1328,7 @@ LayoutViewFunctions::cm_reload () void LayoutViewFunctions::do_transform (const db::DCplxTrans &tr) { - // end move operations, cancel edit operations - view ()->cancel_edits (); - view ()->lay::Editables::transform (tr); + view ()->transform (tr); } void @@ -1545,6 +1541,7 @@ LayoutViewFunctions::cm_sel_scale () void LayoutViewFunctions::cm_sel_move_interactive () { + view ()->cancel_edits (); if (view ()->move_service ()->begin_move ()) { view ()->switch_mode (-1); // move mode } diff --git a/src/layview/layview/layLayoutView_qt.cc b/src/layview/layview/layLayoutView_qt.cc index 7fb3d76e4..7bce16caa 100644 --- a/src/layview/layview/layLayoutView_qt.cc +++ b/src/layview/layview/layLayoutView_qt.cc @@ -1439,6 +1439,17 @@ LayoutView::cancel_edits () LayoutViewBase::cancel_edits (); } +void +LayoutView::finish_edits () +{ + // closes the property dialog + if (mp_properties_dialog) { + mp_properties_dialog->hide (); + } + + LayoutViewBase::finish_edits (); +} + void LayoutView::activate () { diff --git a/src/layview/layview/layLayoutView_qt.h b/src/layview/layview/layLayoutView_qt.h index 4c626a6d6..f81d737ca 100644 --- a/src/layview/layview/layLayoutView_qt.h +++ b/src/layview/layview/layLayoutView_qt.h @@ -459,6 +459,11 @@ class LAYVIEW_PUBLIC LayoutView */ void cancel_edits (); + /** + * @brief Finishes all edit operations and maintains selection + */ + void finish_edits (); + /** * @brief Select all levels of hierarchy available */ diff --git a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc index 893f83eb5..0b19845a1 100644 --- a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc +++ b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFImporter.cc @@ -1084,7 +1084,7 @@ LEFDEFReaderState::read_single_map_file (const std::string &path, std::map purpose_translation; @@ -2011,7 +2011,7 @@ LEFDEFImporter::get_mask (long m) void LEFDEFImporter::read (tl::InputStream &stream, db::Layout &layout, LEFDEFReaderState &state) { - tl::log << tl::to_string (tr ("Reading LEF/DEF file")) << " " << stream.absolute_path (); + tl::log << tl::to_string (tr ("Reading LEF/DEF file")) << " " << stream.absolute_file_path (); m_fn = stream.filename (); diff --git a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFPlugin.cc b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFPlugin.cc index 8fe4c3fa5..3f04e2399 100644 --- a/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFPlugin.cc +++ b/src/plugins/streamers/lefdef/db_plugin/dbLEFDEFPlugin.cc @@ -120,7 +120,7 @@ LEFDEFReader::read_lefdef (db::Layout &layout, const db::LoadLayoutOptions &opti std::string base_path; if (! effective_options.paths_relative_to_cwd ()) { - base_path = tl::dirname (m_stream.absolute_path ()); + base_path = tl::dirname (m_stream.absolute_file_path ()); } db::LEFDEFReaderState state (&effective_options, layout, base_path); @@ -179,7 +179,7 @@ LEFDEFReader::read_lefdef (db::Layout &layout, const db::LoadLayoutOptions &opti if (effective_options.read_lef_with_def ()) { - std::string input_dir = tl::absolute_path (m_stream.absolute_path ()); + std::string input_dir = tl::absolute_path (m_stream.absolute_file_path ()); if (tl::file_exists (input_dir)) { diff --git a/src/rdb/rdb/rdb.cc b/src/rdb/rdb/rdb.cc index 64f8192f4..125f18ec7 100644 --- a/src/rdb/rdb/rdb.cc +++ b/src/rdb/rdb/rdb.cc @@ -1786,7 +1786,7 @@ Database::load (std::string fn) reader.read (*this); } - set_filename (stream.absolute_path ()); + set_filename (stream.absolute_file_path ()); set_name (stream.filename ()); reset_modified (); diff --git a/src/tl/tl/tlExpression.cc b/src/tl/tl/tlExpression.cc index 35eaa01a4..07a76af92 100644 --- a/src/tl/tl/tlExpression.cc +++ b/src/tl/tl/tlExpression.cc @@ -4059,7 +4059,6 @@ Eval::parse (Expression &expr, const std::string &s, bool top) expr = Expression (this, s); tl::Extractor ex (s.c_str ()); - tl::Extractor ex0 = ex; ExpressionParserContext context (&expr, ex); if (top) { @@ -4074,6 +4073,8 @@ Eval::parse (Expression &expr, const std::string &s, bool top) void Eval::parse (Expression &expr, tl::Extractor &ex, bool top) { + ex.skip (); + expr = Expression (this, ex.get ()); tl::Extractor ex0 = ex; @@ -4093,6 +4094,8 @@ Eval::parse (Expression &expr, tl::Extractor &ex, bool top) std::string Eval::parse_expr (tl::Extractor &ex, bool top) { + ex.skip (); + tl::Eval eval (0, true); Expression expr (&eval, ex.get ()); diff --git a/src/tl/tl/tlFileUtils.cc b/src/tl/tl/tlFileUtils.cc index 34aff09ae..342edd070 100644 --- a/src/tl/tl/tlFileUtils.cc +++ b/src/tl/tl/tlFileUtils.cc @@ -254,7 +254,6 @@ static std::vector split_filename (const std::string &fn) const char *cp0 = cp; ++cp; while (*cp && *cp != '.') { - // backslash escaping (ineffective on Windows because that is a path separator) if (*cp == '\\' && cp[1]) { ++cp; } diff --git a/src/tl/tl/tlStream.cc b/src/tl/tl/tlStream.cc index f83be7bfb..2d575fdc2 100644 --- a/src/tl/tl/tlStream.cc +++ b/src/tl/tl/tlStream.cc @@ -425,32 +425,124 @@ InputStream::~InputStream () } } -std::string InputStream::absolute_path (const std::string &abstract_path) +std::string InputStream::absolute_file_path (const std::string &abstract_path) { // TODO: align this implementation with InputStream ctor tl::Extractor ex (abstract_path.c_str ()); -#if defined(HAVE_QT) if (ex.test (":")) { return abstract_path; - } else -#endif -#if defined(HAVE_CURL) || defined(HAVE_QT) - if (ex.test ("http:") || ex.test ("https:")) { - return abstract_path; - } else -#endif - if (ex.test ("pipe:")) { + } else if (ex.test ("http:") || ex.test ("https:") || ex.test ("pipe:") || ex.test ("data:")) { return abstract_path; } else if (ex.test ("file:")) { tl::URI uri (abstract_path); - return tl::absolute_path (uri.path ()); + return tl::absolute_file_path (uri.path ()); } else { return tl::absolute_file_path (abstract_path); } } -const char * +bool InputStream::is_absolute (const std::string &abstract_path) +{ + // TODO: align this implementation with InputStream ctor + + tl::Extractor ex (abstract_path.c_str ()); + if (ex.test (":")) { + return true; + } else if (ex.test ("http:") || ex.test ("https:") || ex.test ("pipe:") || ex.test ("data:")) { + return true; + } else if (ex.test ("file:")) { + tl::URI uri (abstract_path); + return tl::is_absolute (uri.path ()); + } else { + return tl::is_absolute (abstract_path); + } +} + +bool InputStream::is_file_path (const std::string &abstract_path) +{ + tl::Extractor ex (abstract_path.c_str ()); + if (ex.test (":")) { + return false; + } else if (ex.test ("http:") || ex.test ("https:") || ex.test ("pipe:") || ex.test ("data:")) { + return false; + } else { + return true; + } +} + +std::string InputStream::as_file_path (const std::string &abstract_path) +{ + tl::Extractor ex (abstract_path.c_str ()); + if (ex.test (":")) { + return std::string (); + } else if (ex.test ("http:") || ex.test ("https:") || ex.test ("pipe:") || ex.test ("data:")) { + return std::string (); + } else if (ex.test ("file:")) { + tl::URI uri (abstract_path); + return uri.path (); + } else { + return abstract_path; + } +} + +std::string InputStream::combine (const std::string &path1, const std::string &path2) +{ + if (is_absolute (path2)) { + return path2; + } + + tl::Extractor ex (path1); + if (ex.test (":")) { + return path1 + "/" + path2; + } else if (ex.test ("pipe:") || ex.test ("data:")) { + // ignore un-combinable first parts + return path2; + } + + tl::URI uri1 (path1); + tl::URI uri2 (path2); + + if (uri1.scheme ().empty ()) { + if (uri2.scheme ().empty ()) { + return tl::combine_path (path1, path2); + } else { + return tl::combine_path (path1, uri2.path ()); + } + } else { + if (uri2.scheme ().empty ()) { + uri1.set_path (uri1.path () + "/" + tl::replaced (path2, "\\", "/")); + } else { + uri1.set_path (uri1.path () + "/" + uri2.path ()); + } + return uri1.to_abstract_path (); + } +} + +std::string InputStream::relative_path (const std::string &path1, const std::string &path2) +{ + // TODO: align this implementation with InputStream ctor + + tl::Extractor ex (path2); + if (ex.test (":")) { + return path2; + } else if (ex.test ("pipe:") || ex.test ("data:")) { + return path2; + } + + tl::URI uri1 (path1); + tl::URI uri2 (path2); + + // NOTE: only file schemes are supported as of now + if ((uri1.scheme ().empty () || uri1.scheme () == "file") && + (uri2.scheme ().empty () || uri2.scheme () == "file")) { + return tl::relative_path (uri1.path (), uri2.path ()); + } + + return path2; +} + +const char * InputStream::get (size_t n, bool bypass_inflate) { // if deflating, employ the deflate filter to get the data diff --git a/src/tl/tl/tlStream.h b/src/tl/tl/tlStream.h index c738c84ff..a69412aae 100644 --- a/src/tl/tl/tlStream.h +++ b/src/tl/tl/tlStream.h @@ -531,7 +531,7 @@ class TL_PUBLIC InputStream * * Returns an empty string if no absolute path is available. */ - std::string absolute_path () const + std::string absolute_file_path () const { return mp_delegate->absolute_path (); } @@ -549,9 +549,34 @@ class TL_PUBLIC InputStream void close (); /** - * @brief Gets the absolute path for a given URL + * @brief Gets the absolute, abstract path for a given abstract path */ - static std::string absolute_path (const std::string &path); + static std::string absolute_file_path (const std::string &apath); + + /** + * @brief Gets the absolute path for a given abstract path + */ + static bool is_absolute (const std::string &apath); + + /** + * @brief Gets a value indicating whether the path is a file path + */ + static bool is_file_path (const std::string &apath); + + /** + * @brief Gets the file path (no scheme) if it applies to the given abstract path + */ + static std::string as_file_path (const std::string &apath); + + /** + * @brief Combines two abstract paths + */ + static std::string combine (const std::string &apath1, const std::string &apath2); + + /** + * @brief Returns the relative abstract path of path2 vs. path1 + */ + static std::string relative_path (const std::string &apath1, const std::string &apath2); /** * @brief Gets the base reader (delegate) @@ -621,7 +646,7 @@ class TL_PUBLIC inflating_input_stream virtual std::string absolute_path () const { - return m_inflating_stream.absolute_path (); + return m_inflating_stream.absolute_file_path (); } virtual std::string filename () const diff --git a/src/tl/unit_tests/tlStreamTests.cc b/src/tl/unit_tests/tlStreamTests.cc index 156f276c2..50408adaa 100644 --- a/src/tl/unit_tests/tlStreamTests.cc +++ b/src/tl/unit_tests/tlStreamTests.cc @@ -479,3 +479,112 @@ TEST(RefuseToWrite) } } +TEST(AbstractPathFunctions) +{ + EXPECT_EQ (tl::InputStream::absolute_file_path (""), tl::absolute_file_path (".")); + EXPECT_EQ (tl::InputStream::absolute_file_path ("."), tl::absolute_file_path (".")); + EXPECT_EQ (tl::InputStream::absolute_file_path ("pipe:xyz"), "pipe:xyz"); + EXPECT_EQ (tl::InputStream::absolute_file_path ("data:xyz"), "data:xyz"); + EXPECT_EQ (tl::InputStream::absolute_file_path ("https:xyz"), "https:xyz"); + EXPECT_EQ (tl::InputStream::absolute_file_path ("http:xyz"), "http:xyz"); + EXPECT_EQ (tl::InputStream::absolute_file_path (":xyz"), ":xyz"); + EXPECT_EQ (tl::InputStream::absolute_file_path ("file:xyz"), tl::absolute_file_path ("xyz")); + EXPECT_EQ (tl::InputStream::absolute_file_path ("xyz"), tl::absolute_file_path ("xyz")); + EXPECT_EQ (tl::InputStream::absolute_file_path ("xyz/uvw"), tl::absolute_file_path ("xyz/uvw")); + EXPECT_EQ (tl::InputStream::absolute_file_path ("/xyz/uvw"), tl::absolute_file_path ("/xyz/uvw")); + tl::file_utils_force_windows (); + EXPECT_EQ (tl::InputStream::absolute_file_path ("xyz\\uvw"), tl::absolute_file_path ("xyz\\uvw")); + EXPECT_EQ (tl::InputStream::absolute_file_path ("\\\\server\\xyz\\uvw"), "\\\\server\\xyz\\uvw"); + EXPECT_EQ (tl::InputStream::absolute_file_path ("C:\\xyz\\uvw"), "C:\\xyz\\uvw"); + tl::file_utils_force_reset (); + + EXPECT_EQ (tl::InputStream::is_absolute (""), false); + EXPECT_EQ (tl::InputStream::is_absolute ("."), false); + EXPECT_EQ (tl::InputStream::is_absolute ("pipe:xyz"), true); + EXPECT_EQ (tl::InputStream::is_absolute ("data:xyz"), true); + EXPECT_EQ (tl::InputStream::is_absolute ("https:xyz"), true); + EXPECT_EQ (tl::InputStream::is_absolute ("http:xyz"), true); + EXPECT_EQ (tl::InputStream::is_absolute (":xyz"), true); + EXPECT_EQ (tl::InputStream::is_absolute ("file:xyz"), false); + EXPECT_EQ (tl::InputStream::is_absolute ("xyz"), false); + EXPECT_EQ (tl::InputStream::is_absolute ("xyz/uvw"), false); + tl::file_utils_force_linux (); + EXPECT_EQ (tl::InputStream::is_absolute ("/xyz/uvw"), true); + tl::file_utils_force_windows (); + EXPECT_EQ (tl::InputStream::is_absolute ("xyz\\uvw"), false); + EXPECT_EQ (tl::InputStream::is_absolute ("\\\\server\\xyz\\uvw"), true); + EXPECT_EQ (tl::InputStream::is_absolute ("c:\\xyz\\uvw"), true); + tl::file_utils_force_reset (); + + tl::file_utils_force_windows (); + EXPECT_EQ (tl::InputStream::combine ("a", ""), "a"); + EXPECT_EQ (tl::InputStream::combine ("", "b"), "\\b"); + EXPECT_EQ (tl::InputStream::combine ("a", "b"), "a\\b"); + EXPECT_EQ (tl::InputStream::combine ("a", "b/c"), "a\\b/c"); + EXPECT_EQ (tl::InputStream::combine ("a", "b\\c"), "a\\b\\c"); + EXPECT_EQ (tl::InputStream::combine ("a", "data:abc"), "data:abc"); + EXPECT_EQ (tl::InputStream::combine ("data:a", "b"), "b"); + EXPECT_EQ (tl::InputStream::combine ("pipe:a", "b"), "b"); + EXPECT_EQ (tl::InputStream::combine (":a", "b"), ":a/b"); + EXPECT_EQ (tl::InputStream::combine ("https://a", "b"), "https://a/b"); + EXPECT_EQ (tl::InputStream::combine ("https://a", "https:b"), "https:b"); + EXPECT_EQ (tl::InputStream::combine ("a", "https:b"), "https:b"); + EXPECT_EQ (tl::InputStream::combine ("a", "file:b"), "a\\b"); + EXPECT_EQ (tl::InputStream::combine ("a", "file:\\b"), "file:\\b"); + EXPECT_EQ (tl::InputStream::combine ("file:a", "file:b"), "file:a/b"); + EXPECT_EQ (tl::InputStream::combine ("file:a", "file:b/c"), "file:a/b/c"); + EXPECT_EQ (tl::InputStream::combine ("file:a", "b\\c"), "file:a/b/c"); + tl::file_utils_force_linux (); + EXPECT_EQ (tl::InputStream::combine ("a", "b"), "a/b"); + EXPECT_EQ (tl::InputStream::combine ("", "b"), "/b"); + EXPECT_EQ (tl::InputStream::combine ("a", "b/c"), "a/b/c"); + EXPECT_EQ (tl::InputStream::combine ("a", "data:abc"), "data:abc"); + EXPECT_EQ (tl::InputStream::combine ("data:a", "b"), "b"); + EXPECT_EQ (tl::InputStream::combine ("pipe:a", "b"), "b"); + EXPECT_EQ (tl::InputStream::combine (":a", "b"), ":a/b"); + EXPECT_EQ (tl::InputStream::combine ("https://a", "b"), "https://a/b"); + EXPECT_EQ (tl::InputStream::combine ("https://a", "https:b"), "https:b"); + EXPECT_EQ (tl::InputStream::combine ("a", "https:b"), "https:b"); + EXPECT_EQ (tl::InputStream::combine ("a", "file:b"), "a/b"); + EXPECT_EQ (tl::InputStream::combine ("a", "file:/b"), "file:/b"); + EXPECT_EQ (tl::InputStream::combine ("file:a", "file:b"), "file:a/b"); + EXPECT_EQ (tl::InputStream::combine ("file:a", "file:b/c"), "file:a/b/c"); + EXPECT_EQ (tl::InputStream::combine ("file:a", "b/c"), "file:a/b/c"); + tl::file_utils_force_reset (); + + tl::file_utils_force_linux (); + EXPECT_EQ (tl::InputStream::relative_path ("", "file:/a/b/c"), "/a/b/c"); + EXPECT_EQ (tl::InputStream::relative_path (".", "file:/a/b/c"), "/a/b/c"); + EXPECT_EQ (tl::InputStream::relative_path ("https://x", "a/b/c"), "a/b/c"); + EXPECT_EQ (tl::InputStream::relative_path ("file:/a/b", "file:/a/b/c"), "c"); + EXPECT_EQ (tl::InputStream::relative_path ("/a/b", "/a/b/c"), "c"); + EXPECT_EQ (tl::InputStream::relative_path ("/a/b", "/x/b/c"), "/x/b/c"); + EXPECT_EQ (tl::InputStream::relative_path ("file:/a/b", "file:/a/b/c"), "c"); + EXPECT_EQ (tl::InputStream::relative_path ("/a/b", "/a/b/c"), "c"); + tl::file_utils_force_windows (); + EXPECT_EQ (tl::InputStream::relative_path ("/a/b", "/a/b/c"), "c"); + EXPECT_EQ (tl::InputStream::relative_path ("/a/b", "\\a\\b\\c\\d"), "c\\d"); + tl::file_utils_force_reset (); + + EXPECT_EQ (tl::InputStream::is_file_path (""), true); + EXPECT_EQ (tl::InputStream::is_file_path (":abc"), false); + EXPECT_EQ (tl::InputStream::is_file_path ("pipe:abc"), false); + EXPECT_EQ (tl::InputStream::is_file_path ("data:abc"), false); + EXPECT_EQ (tl::InputStream::is_file_path ("http:abc"), false); + EXPECT_EQ (tl::InputStream::is_file_path ("file:abc"), true); + EXPECT_EQ (tl::InputStream::is_file_path ("a/b/c"), true); + tl::file_utils_force_windows (); + EXPECT_EQ (tl::InputStream::is_file_path ("a\\b\\c"), true); + tl::file_utils_force_reset (); + + EXPECT_EQ (tl::InputStream::as_file_path (""), std::string ()); + EXPECT_EQ (tl::InputStream::as_file_path (":abc"), std::string ()); + EXPECT_EQ (tl::InputStream::as_file_path ("pipe:abc"), std::string ()); + EXPECT_EQ (tl::InputStream::as_file_path ("data:abc"), std::string ()); + EXPECT_EQ (tl::InputStream::as_file_path ("http:abc"), std::string ()); + EXPECT_EQ (tl::InputStream::as_file_path ("file:abc"), "abc"); + EXPECT_EQ (tl::InputStream::as_file_path ("a/b/c"), "a/b/c"); + tl::file_utils_force_windows (); + EXPECT_EQ (tl::InputStream::as_file_path ("a\\b\\c"), "a\\b\\c"); + tl::file_utils_force_reset (); +}